Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Need guidance with FilteredSelectMultiple widget

I am sorry if it question might turn to be little broad, but since I am just learning django (and I am just hobbyist developer) I need some guidance which, I hope, will help someone like me in the future since I could not find any clear and easily comprehensible guide on using this widget. With your answers and help I will try to make this question thread at least guide-ish.

Material I found somewhat helpful for this topic:

Django multi-select widget?

Django: Replacement for the default ManyToMany Widget of Forms

Django's FilteredSelectMultiple widget only works when logged in

Django FilteredSelectMultiple not rendering on page

Use the Django admin app's FilteredSelectMultiple widget in form

Get the chosen values from FilteredSelectMultiple widget in Django

Django FilteredSelectMultiple Right Half Does Not Render

There were few others links, but they did not made anything clearer or added new information so I won't mention them.

Here is what I managed to understand (please correct me if I am wrong or add anything that I missed):

To create FilteredSelectMultiple widget firs I need to amend forms.py (as in any other widget creation process). Amended forms.py should have from django.contrib.admin.widgets import FilteredSelectMultiple import and Media class. forms.py code should look like this (please correct me, because probably it is wrong somewhere):

from django import forms
from catalog.models import DrgCode  
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.conf import settings #without it I get an error that settings not defined

class CalculatorForm(forms.Form):
    drg_choice = forms.ModelMultipleChoiceField(queryset=DrgCode.objects.all(), widget=FilteredSelectMultiple("Somethings", is_stacked=False), required=True)

    class Media:
        css = {
            'all': (os.path.join(settings.BASE_DIR, '/static/admin/css/widgets.css'),),
        }
        js = ('/admin/jsi18n',)

Questions about this part:

  1. Am I right about django.conf import? Since I did not see it imported in any material I found. Answer: during my test I determined that django.conf import is necessary if using settings.BASE_DIR part. In various sources there was two ways of writing css path, this one worked for me.
  2. Do I need to create widgets.css and corresponding directory? Or django will find it itself? Since there is no such file or directory generated after I created skeleton-website using django-admin startproject cmd? Answer: No. There is no need to create widgets.css or any of the files since django finds them itself.
  3. Same question as previous for jsi18n part. Also what is this? I assume its javascript file, but it has no extension for some reason. Also I cannot find it anywhere? Should I create it? How to do that? Or I can copy it from somewhere? Partial answer: no need to create it. Just point at it in urls.py. Still do not know exactly that kind of file it is (or where it is)

After amending forms.py I should ammend urls.pyby adding url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', name='jsi18n')

So urls.py now look like this:

from django.urls import path
from . import views
from django.conf.urls import url 


urlpatterns = [
    path('', views.index, name='index'),
    'django.views.i18n.javascript_catalog',
    name='jsi18n'),
]

Questions:

  1. Am I doing it wright or should I just add it below urlpatterns? Answer: This method if fine.

Now I need to set HTML template file for form to render (like in any other case). Code for it (file named DrgCalculator.html):

{% extends "base_generic.html" %}
   <script type="text/javascript" src="{% url 'jsi18n' %}" > </script>

  {{ form.media }}

  <form enctype="multipart/form-data" method="POST">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit" class="save btn btn-default">Submit</button>
  </form>
  1. This part seems more or less clear. But maybe I should amend something or know about? Answer: Should be changed. Will write full code below.

Lastly I need to adjust views.py to set where this form and everything related with it happens.

From what I understand code in this part is more or less is not directly related with widget, but to complete everything and make working example I will use code I leaned/got in this (very good) django tutorial:

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse

from catalog.models import DrgCode
from catalog.forms import CalculatorForm

def DrgCalculator(request):
    if request.method == 'POST':
        form = CalculatorForm(request.POST)
        if form.is_valid():
            return render(request, 'DrgCalculator.html')
    context = {
        'form': form,
    }
    return render(request, 'DrgCalculator.html', context)

Questions:

  1. Any remarks on this part of the code? Answer: Missing else: form = DrgCalculator(). Will write amended code below.
  2. How I will access values which user choose using FilteredSelectMultiple? I imagine I should clean data in forms.py like with other widgets. So I should add nested function below to my class CalculatorForm in forms.py am I right? Answer: Yes, data should be cleaned like in other cases. Function is correct.

    def clean_CalculatorForm(self): drg_choice = self.cleaned_data['drg_choice'] return drg_choice

  3. Data I will get after cleaning will be list or dictionary? Am I right? Answer: No, from this widget user input received as QuerySet

That is all my questions, sorry for long thread, I tried to make it as clear as possible. If I need to clarify something, please let me know. I will try to edit and update this, to make it friendly for people who will read it in future.

EDIT1: Answered some of my questions.

EDIT2: Answered rest of my questions.

like image 644
Gexas Avatar asked Jan 25 '23 21:01

Gexas


1 Answers

After spending few days of research and testing I managed to get FilteredSelectMultiple widget working outside admin page in user form. As promised in question, I will try to synthesize my accumulated knowledge into some sort of guide which, I hope, will help someone like me in the future. Please note that I am far from professional (just a hobbyist with no computer engineering background to be precise) so my observations might not be exactly correct or the way I done it might not the best one. Despite that it might help you to get on the right track.

So to begin with FilteredSelectMultiple widget first of all - forms.py have to be amended by importing widget, creating field for it (similar like with regular widgets) and adding nested Media class. Code example:

from django.contrib.admin.widgets import FilteredSelectMultiple    

class DrgCalculator(forms.Form):
        drg_choise = forms.ModelMultipleChoiceField(queryset=DrgCode.objects.all(),
                                                          label="Something",
                                                          widget=FilteredSelectMultiple("Title", is_stacked=False),
                                                          required=True)
        
        class Media:
            css = {
                'all': ('/static/admin/css/widgets.css',),
            }
            js = ('/admin/jsi18n',)
        
        def clean_drg_choise(self):
            drg_choise = self.cleaned_data['drg_choise']
            return drg_choise

As I determined during my test and research class Media should be copied as written and require no changes for widget to work. Files mentioned in this class will be found by django itself so no need to search and copy them (like told in some of the material I read).

After creating form urls.py should be amended. During testing I found out, that in newer django versions (or at lest one I used) javascript_catalog is renamed and url provided cannot be string. So code should look like this:

from django.urls import path
from . import views
from django.conf.urls import url
from django import views as django_views

urlpatterns = [
    url(r'^jsi18n/$', django_views.i18n.JavaScriptCatalog.as_view(), name='jsi18n'),
]

Now for the htlm template I am sure that there is more ways of doing it so I just provide example:

{% extends "base_generic.html" %}

{% block content %}
<div id='frame'>
    <form action="" method="post">
        <div id='sk_body'>
            <fieldset>
            <legend>Fill required fields</legend>      
                <form>
                    {% csrf_token %}
                    <table>
                        {{ form.media }}
                        {{ form.as_table }}
                        <script type="text/javascript" src="{% url 'jsi18n' %}"></script>
                    </table>
                        <input type="submit" value="Count">
                </form>
            </fieldset>
        </div>
    </form>
</div>
{% endblock %}

To receive data from this widget fairly standard code in views.py should be used, example code I used:

def DRG_calcualtor(request):
    if request.method == 'POST':
        form = DrgCalculator(request.POST)
        if form.is_valid():
            choosen_drg = form.cleaned_data['drg_choise'] #result as QuerySet
            choosen_drg_list = list([str(i) for i in choosen_drg]) #you can convert it to list or anything you need             
                  
            return render(request, 'DRGcalculator_valid.html')
           
        context = {
            'form': form,
            }
        return render(request, 'DRGcalculator.html', context)
    
    else:
        form = DrgCalculator()
    context = {
        'form': form,
        }
    return render(request, 'DRGcalculator.html', context)
like image 149
Gexas Avatar answered Jan 28 '23 11:01

Gexas