Auto-generate form fields for a Form in django - python

I have some models and I want to generate a multi-selection form from this data.
So the form would contain an entry for each category and the choices would be the skills in that category.
models.py
class SkillCategory(models.Model):
name = models.CharField(max_length=50)
class Skill(models.Model):
name = models.CharField(max_length=50)
category = models.ForeignKey(SkillCategory)
Is there a way to auto-generate the form fields?
I know I can manually add a 'SkillCategory' entry in the form for each SkillCategory, but the reason to have it as a model is so skills and skillcategories can be edited freely.
I want to do something like this:
(I tried this, but didn't get it to work, don't remember the exact error...)
forms.py
class SkillSelectionForm(forms.Form):
def __init__(*args, **kwargs):
super(SkillSelectionForm, self).__init__(*args, **kwargs)
for c in SkillCategory.objects.all():
category_skills = [(pk, s.name) for s in c.skill_set.all()]
setattr(self, c.name, forms.MultipleChoiceField(choices=category_skills, widget=forms.CheckboxSelectMultiple))
SOLUTION
This creates a form field entry using the SkillCategory.name and assigns choices as those in Skill. field_name/display_name are used to avoid issues with non-ascii category names.
forms.py
def get_categorized_skills():
skills = {}
for s in Skill.objects.values('pk', 'name', 'category__name').order_by('category__name'):
if s['category__name'] not in skills.keys():
skills[s['category__name']] = []
skills[s['category__name']].append((s['pk'], s['name']))
return skills
class SkillSelectionForm(forms.Form):
def __init__(self, *args, **kwargs):
super(SkillSelectionForm, self).__init__(*args, **kwargs)
skills = get_categorized_skills()
for idx, cat in enumerate(skills.keys()):
field_name = u'category-{0}'.format(idx)
display_name = cat
self.fields[field_name] = forms.MultipleChoiceField(choices=skills[cat], widget=forms.CheckboxSelectMultiple, label=display_name)

Okay so you can't set fields like that on forms.Form, for reasons which will become apparent when you see DeclarativeFieldsMetaclass, the metaclass of forms.Form (but not of forms.BaseForm). A solution which may be overkill in your case but an example of how dynamic form construction can be done, is something like this:
base_fields = [
forms.MultipleChoiceField(choices=[
(pk, s.name) for s in c.skill_set.all()
]) for c in SkillCategory.objects.all()
]
SkillSelectionForm = type('SkillSelectionForm', (forms.BaseForm,), {'base_fields': base_fields})

Take a look at creating dynamic forms in Django, from b-list.org and uswaretech.com. I've had success using these examples to dynamically create form content from models.

What you want is a Formset. This will give you a set of rows, each of which maps to a specific Skill.
See the Formset documentation and the page specifically on generating formsets for models.

Related

Is there any way to customize the order of multiple fields for forms.Form class with a field initiated by __init__?

When I render the form to a HTML template,
a certain field of form which is initiated by init is ordered always at bottom of table, even though I defined the field at the middle of form class.
Is there any way or method to customize the order of fields in the form where a initiated field exists by init.
I wanna put the field in the middle of form table in HTML template.
A screenshot of the rendered template:
In the screenshot,
the field "category_name" is ordered at the bottom of tag
I wanna change the order to the middle of table.
I am using Django 2.2 and python 3.7 on Windows 10.
Thanks
from django import forms
from .models import Category_name
class MakePurchaseLog(forms.Form):
buy_date = forms.DateField(input_formats=['%Y-%m-%d'])
shop = forms.CharField(max_length=50)
def __init__(self, user, *args, **kwargs):
super(MakePurchaseLog, self).__init__(*args, **kwargs)
self.fields['category_name'] = forms.ChoiceField(
choices = [(item.category_name, item.category_name) \
for item in Category_name.objects. \
filter(owner=user)])
goods_name = forms.CharField(max_length=50)
price = forms.IntegerField(min_value=0)
memo = forms.CharField(max_length=50, required=False)
field_order = ['category_name']
The fact that the __init__ is placed in the middle of the class, will not make any difference, since this is a function, and the evaluation is thus "postponed" until you actually create a MakePurchaseLog form object.
I think the most elegant solution here is to just already define the field in your form, and then in the __init__ function alter the choices, like:
class MakePurchaseLog(forms.Form):
buy_date = forms.DateField(input_formats=['%Y-%m-%d'])
shop = forms.CharField(max_length=50)
category_name = forms.ChoiceField()
goods_name = forms.CharField(max_length=50)
price = forms.IntegerField(min_value=0)
memo = forms.CharField(max_length=50, required=False)
def __init__(self, user, *args, **kwargs):
super(MakePurchaseLog, self).__init__(*args, **kwargs)
self.fields['category_name'].choices = [
(item.category_name, item.category_name)
for item in Category_name.objects.filter(owner=user)
]
We thus populate the choices of the category_name field in the __init__ function, but we define the field already at the class level.

Django Models design: many-to-many relationship with specific needs

I am in the process of designing a couple of new models for my django app and this is what I need:
class Bookmaker(models.Model):
name = models.CharField(max_length=50)
accepted_countries = ?
restricted_countries = ?
class Country(models.Model):
name = models.CharField(max_length=50)
bookmakers = ?
So I need a model Bookmaker and a model Country and they need to be related, BUT every bookmaker should have a list of countries that are accepted and a list of countries that are excluded.
The question marks are both in the Country and in the Bookmaker models as I'm not sure which way the relation should go.
Not sure if I'm on the right path, but I'm thinking that I need a couple of many-to-many relationships.. And, which is the right way (if any) to use the same Country model (those will be different instances) in both accepted_countries and restricted_countries?
Thanks.
You should use two many to many relations with related_name to separate them:
class Bookmaker(models.Model):
name = models.CharField(max_length=50)
accepted_countries = models.ManyToManyField(Country, related_name="accepted_for")
restricted_countries = models.ManyToManyField(Country, related_name="restricted_for")
You can then use the reverse relation as:
bookmakers_for_which_county_is_accepted = Country.objects.values('accepted_for').distinct()
bookmakers_for_which_county_is_restricted = Country.objects.values('restricted_for').distinct()
docs
You need indeed some many to many:
class Bookmaker(models.Model):
name = models.CharField(max_length=50)
accepted_countries = models.ManyToMany('Country',related_name='accepted')
restricted_countries = models.ManyToMany('Country', related_name= 'restricted')
class Country(models.Model):
name = models.CharField(max_length=50)
bookmakers = models.ManyToMany(Bookmaker)
Then if you create a form to edit a bookmaker you will be abble to add the name, the accepted and restricted countries:
forms.py
class BookmakerForm(models.ModelForm):
class Meta:
model = Bookmaker
fields = ['name', 'accepted_countries', 'restricted_countries']
#for having a better default display you can change the widget:
self __init__(self, *args, **kwargs):
super(BookmakerForm, self).__init__(*args, **kwargs)
self.fields['accepted_countries'].widget = CheckboxSelectMultiple()
self.fields['restricted_countries'].widget = CheckboxSelectMultiple()
In the view by default if you just need to check if form.is_valid() and save. django modelform will manage the intermediary step for you.
The bookmakers field in Country allows you to retrieve all the bookmaker associated to a country without having to check in both accepted and restricted (it's more for convenience).
But you will need to add the bookmaker to that list on you view like:
class CreateBookmakerView(CreateView):
model = Bookmaker
form_class = BookmakerForm
success_url = reverse_lazy('your success url name here')
def form_valid(self,form):
bookmaker = form.save()
bookmaker.country_set.add(bookmaker)
bookmaker.save()
return HttpResponseRedirect(self.get_success_url())

Django Form Automatically select first option if only one choice available

Is there an easy way to select the first option of a dropdown in a Django form, in cases where there is only one option available?
With easy I mean a solution that doesn't require defining an own widget, which would be quite repetitive to change in all our (model) forms.
Update because it seems like the initial question was unclear:
I want the initital option only to be selected if there is one option available. And the the way to do that is non-obvious to me, if the options are foreign key references to another model:
class Category(models.Model):
name = CharField(...)
class Post(models.Model):
category = ForeignKey(Category)
class PostForm(forms.ModelForm):
class Meta:
fields = '__all__'
[...]
Now I want the category field in the PostForm to be autoselected to the first category, if only one instance is present in the database and be -------- if there a two or more categories
What about using this :
Your_Dropdown = forms.TypedChoiceField(choices=your choises, initial='FIRST_OPTION')
As shown in documentation: Here
I don't think any explanation is required, have a look at following code
post_type_choices = (
('article', 'article'),
('tip', 'tip'),
('snippet', 'snippet'),
)
class Post(models.Model):
post_type = models.CharField(
max_length=10, choices=post_type_choices,default='article')
I did it in the get_form() like this:
def get_form(self, form_class=form_class):
form = super(WPWeekSubjectCreateView, self).get_form(form_class)
....
subjects = Subject.objects.filter(
schoolyear=schoolyear,
pk__in=subject_bearings,
lesson__teachers=teacher
).order_by('abbreviation').distinct()
form.fields['subject'].queryset = subjects
if subjects.count() == 1:
form.fields['subject'].initial = subjects.first()
return form

How to create a dynamic Django choice field

I am trying to generate a list to populate choices for a form. This list will be dynamic and will depend on what choices have been created by other users. Here is an example of the model the list should derive from:
#models.py
class User(models.Model):
brewery_you_work_for = models.CharField(choises=ALL_THE_BREWERIES_IN_THE_WORLD)
username = models.CharField()
I want the form to be something like this:
#forms.py
class BestBrewery(forms.Form):
vote = forms.ChoiceField(choices=BREWERIES_A_USER_WORKS_FOR)
What I want to do is have a list of all the breweries in the world for some users to select as the brewery they work for. Then I want to generate a list for other users to vote as the best brewery.
Lets say I have 3 users that claim they work the Sierra Nevada, 2 users that claim they work for Budweiser, and 5 users that claim they work for Coors.
I want to generate a list of breweries that would look like this:
(
'Budweiser',
'Coors,
'Sierra Nevada',
)
Note the alphabetical order and no repeated brewery listings.
You need to pass an extra user argument when initialising the form. Normally like this if you are using view functions:
form = BestBrewery(request.user)
If you are using Class Based Views you should override the get_form_kwargs method in your view:
def get_form_kwargs(self):
kwargs = super(MyView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Your form should look like this:
class BestBrewery(forms.Form):
def __init__(self, user, *args, **kwargs):
super(BestBrewery, self).__init__(*args, **kwargs)
self.fields['vote'].choices = (
user.brewery_you_work_for,
user.get_brewery_you_work_for_display()
)
Just note that to override the choices field you need to provide tuples containing the value for the form and the text to be shown in the dropdown.
Well, I think u can set "choices" as result of function.
I tried like that:
def yolo():
# there we will get stats from db and return choices depends on stats
first_query = 1 # there U will use more complicated logic :)
second_query = 3
if first_query > second_query:
return (1, 2, 3)
else:
return (1, 3)
class Event(models.Model):
date = models.DateField(primary_key=True)
count_of_updates = models.SmallIntegerField(default=0)
yolos = models.CharField(max_length=255, choices=yolo())
And, for being sure U can check ur choices with something ugly:
choices = s._meta.get_field_by_name('yolos')[0].choices
print choices
Order and other things u can set in func logic :)
In your Form, you can change the choices of your field in the init.
class BestBrewery(forms.Form):
vote = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(BestBrewery, self).__init__(*arg, **kwargs)
# get the choices from where you need
choices = (...)
self.fields['vote'].choices = choices

Adding 'all' value to a foreign key form field based on model?

I am building a filter for my website where people can filter by cuisine. In order to achieve this I used a model form to receive input information that sets the filter variable in a query in my view. However as you can see in the image linked below, the default select for my cuisine categories is '-------' .
How would I go about changing this to say the words 'all' and setting a value so my filter queries everything for those categories? I think it has something to do with using a form method but I have been unable to understand what is actually happening in some of the examples.
Here is my simple code
Models
class Cuisine(models.Model):
name = models.CharField()
def __str__(self):
return self.name
class Food(models.Model):
name = models.CharField()
cuisine = models.ForeignKey(Cuisine)
def __str__(self):
return self.name
Views
def home_page(request):
if request.method == 'GET':
form = FilterForm(request.GET)
if form.is_valid():
cuisine = form.cleaned_data['cuisine']
food = get_list_or_404(Food, cuisine__pk=cuisine.pk)
return render('base.html', {'food': food, 'form':form})
else:
form = FilterForm()
return render('base.html', {'form':form})
Form
class FilterForm(forms.ModelForm):
class Meta:
model = Cuisine
fields = ('name')
I wouldn't use a modelform here. You only have one field, and you're not using it to create or edit instances of Food or Cuisine. It would be simpler to use a manual form with a ModelChoiceField, to which you can pass the empty_label parameter.
class FilterForm(forms.Form):
cuisine = forms.ModelChoiceField(queryset=Cuisine.objects.all(),
empty_label="All")
(Note you could do this with the ModelForm as well, but that just makes it even more pointless, as you are now not using any of the ModelForm functionality.)

Categories