Django (1.2) Forms: ManyToManyField Help Text - python

I hope I'm wrong, but it looks to me like the only way to have no help_text for a ManyToManyField is write an __init__ method for the form and overwrite self.fields[fieldname].help_text. Is that really the only way? I prefer to use CheckboxSelectMultple widgets, so am I really going to have to define an __init__ method for any form that uses a ManyToManyField?
class ManyToManyField(RelatedField, Field):
description = _("Many-to-many relationship")
def __init__(self, to, **kwargs):
#some other stuff
msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
self.help_text = string_concat(self.help_text, ' ', msg)

class Item(models.Model):
...
category = models.ManyToManyField(Category, null=True,blank=True)
category.help_text = ''
...

In a regular form:
MyForm.base_fields['many_to_many_field'].help_text = ''
If you want to change the (i18n) string:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__( *args, **kwargs)
self.base_fields['many_to_many_field'].help_text = _('Choose at least one stuff') # or nothing
Tested with django 1.6

You are not wrong. I ran into this problem myself and I did create my own ManyToManyField in order to get around this.
Here is a related bug that I commented on: http://code.djangoproject.com/ticket/6183

you can also do it in your Admin class by overriding get_form:
class FooAdmin(ModelAdmin):
...
def get_form(self, request, obj=None, **kwargs):
form = ModelAdmin.get_form(self, request, obj=obj, **kwargs)
form.base_fields['bar'].widget = CheckboxSelectMultiple()
form.base_fields['bar'].help_text = ''
return form

Related

Form Problems - Setting Initial Value

I am trying to set the initial value of a field on a form. The field is not part of the model, but when I try and set it to a value the field is blank. From my research it could be because the form is "bound" which makes some sense to me, but in this case the field is not part of the model.
My form:
#Form for editing profile
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid, required=False)
The model:
class Cat(models.Model):
name = models.CharField(max_length=34,null=False)
From the view it is called like this:
catform = CatForm(request.POST, pictureid=instance.id)
I was expecting it to set the field to the value of the initial attribute, but it doesn't. I have tried testing it by directly adding a string, but doesn't set.
This is what seems to be working for me:
class CatForm(forms.ModelForm):
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid)
I also needed to drop the "request.POST" from the call to this when initiating the form.
If you want to render the pictureid in GET request, then you can try like this:
catform = CatForm(initial={'pictureid': instance.id})
For GET request, you don't need to override the __init__ method.
But, if you want to use the Catform in POST request, to use the value of pictureid somewhere else(lets say in save method), then you will need to override __init__ method here.
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.pictureid = picid
def save(self, *args, **kwargs):
print(self.pictureid) # if you want to use it in save method
return super().save(*args, **kwargs)

How to add an initial/default value using Django Filters?

How can I add an initial/default value when using Django Filters?
For example, something like this initial=False
class UserFilter(django_filters.FilterSet):
archive = django_filters.BooleanFilter(initial=False)
class Meta:
model = User
fields = ['archive']
I've tired to override the __init__ but this does not appear to work.
You can try overriding the __init__ method of UserFilter:
def __init__(self, *args, **kwargs):
super(UserFilter, self).__init__(*args, **kwargs)
self.form.initial['archive'] = False
For DRF you can try override __init__:
def __init__(self, *args, **kwargs):
kwargs['data']._mutable = True
if 'archive' not in kwargs['data']:
kwargs['data']['archive'] = False
kwargs['data']._mutable = False
super(UserFilter, self).__init__(*args, **kwargs)
But you should read django-filter.readthedocs.io...using-initial-values-as-defaults
I had a similar problem in that I wanted to preserve the users selections even after they had navigated away from the page. I implemented my solution in the Filter View as follows...
class BucketLookupView(UserPassesTestMixin,SingleTableMixin,FilterView):
template_name = "finance/planning-bucket-lookup.html"
model = finance_models.SalesOrderBucket
filterset_class = finance_filters.BucketFilter
table_class = finance_tables.BucketTable
def get_filterset_kwargs(self,*args):
kwargs = super().get_filterset_kwargs(*args)
if kwargs['data']:
bucket_filter_data = kwargs['data']
self.request.session['bucket_filter_data']= bucket_filter_data
else:
if 'bucket_filter_data' in self.request.session.keys():
kwargs['data']=self.request.session['bucket_filter_data']
return kwargs
Here is an approximate code snippet I used:
def list(self, request, *args, **kwargs):
filters = QueryDict('is_archive=true', mutable=True)
filters.update(request.GET)
You build a new QueryDict as request.GET is immutable. Not that efficient, but looks pretty to me.
Here is a remix of #IlyaPetukhov's answer that I find easier to read:
def __init__(self, data=None, *args, **kwargs):
if data is not None:
data = data.copy()
data.setdefault("archive", False)
super(UserFilter, self).__init__(data, *args, **kwargs)
If you're uncomfortable with data = data.copy(), you can replace it with data._mutable = True; just know that it's not officially supported by Django.
form.cleaned_data is using to add filters to the queryset and no initial value is set for cleaned data.
The solution could be to override form property:
#property
def form(self) -> ModelForm:
form = super().form
form.data = form.data.copy()
form.data.setdefault("archive", False)
return form

ModelChoiceField initial value in change form

First the code:
class CommentForm(forms.ModelForm):
categories = forms.ModelChoiceField(queryset = Category.objects.all(), required = False)
class CommentAdmin(admin.ModelAdmin):
form = CommentForm
When I'm editing my comment I'd like it categories field have the initial value of what's been selected when I saved it for the last time. How do I do that?
def get_form(self, *args, **kwargs):
f = super(CommentAdmin, self).get_form(*args, **kwargs)
f.base_fields['categories'].initial = 1
return f
This code placed in CommentAdmin did the trick...
EDIT:
def __init__(self, *args, **kwargs):
super(CommentForm, self).__init__(*args, **kwargs)
self.fields['categories'].initial = self.instance.object_id
Or this code placed in CommentForm
You want to have the current model value selected by default in the generated form? If that's the case I think what you are looking for in your view is
form = CommentForm(instance = commentinstance)
Where commentinstance is the instance that you are editing.
(This would be form = CommentForm(request.POST, instance = commentinstance) in case of a POST request)
EDIT:
If you want to do this in the form, you can just provide the instance argument from __init__, like so:
def __init__(self, *args, **kwargs):
instance = kwargs.pop('instance', YOUR_DEFAULT_INSTANCE)
super(CommentForm, self).__init__(instance = instance, *args, **kwargs)
That even leaves the default instance if you do provide one from your view.
I guess there are a few ways to solve this.
Here is how I done before:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
if 'ref' in kwargs:
ref = kwargs['ref']
item = MyModel.objects.get(pk=ref)
kwargs['instance'] = item
super(MyForm, self).__init__(*args, **kwargs)
class Meta:
model = MyModel
The important part is to put your populated model object into the keyword variable instance.

Creating a dynamic choice field

I'm having some trouble trying to understand how to create a dynamic choice field in django. I have a model set up something like:
class rider(models.Model):
user = models.ForeignKey(User)
waypoint = models.ManyToManyField(Waypoint)
class Waypoint(models.Model):
lat = models.FloatField()
lng = models.FloatField()
What I'm trying to do is create a choice Field whos values are the waypoints associated with that rider (which would be the person logged in).
Currently I'm overriding init in my forms like so:
class waypointForm(forms.Form):
def __init__(self, *args, **kwargs):
super(joinTripForm, self).__init__(*args, **kwargs)
self.fields['waypoints'] = forms.ChoiceField(choices=[ (o.id, str(o)) for o in Waypoint.objects.all()])
But all that does is list all the waypoints, they're not associated with any particular rider. Any ideas? Thanks.
you can filter the waypoints by passing the user to the form init
class waypointForm(forms.Form):
def __init__(self, user, *args, **kwargs):
super(waypointForm, self).__init__(*args, **kwargs)
self.fields['waypoints'] = forms.ChoiceField(
choices=[(o.id, str(o)) for o in Waypoint.objects.filter(user=user)]
)
from your view while initiating the form pass the user
form = waypointForm(user)
in case of model form
class waypointForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(waypointForm, self).__init__(*args, **kwargs)
self.fields['waypoints'] = forms.ModelChoiceField(
queryset=Waypoint.objects.filter(user=user)
)
class Meta:
model = Waypoint
There's built-in solution for your problem: ModelChoiceField.
Generally, it's always worth trying to use ModelForm when you need to create/change database objects. Works in 95% of the cases and it's much cleaner than creating your own implementation.
the problem is when you do
def __init__(self, user, *args, **kwargs):
super(waypointForm, self).__init__(*args, **kwargs)
self.fields['waypoints'] = forms.ChoiceField(choices=[ (o.id, str(o)) for o in Waypoint.objects.filter(user=user)])
in a update request, the previous value will lost!
You can declare the field as a first-class attribute of your form and just set choices dynamically in __init__:
class WaypointForm(forms.Form):
waypoints = forms.ChoiceField(choices=[])
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
waypoint_choices = [(o.id, str(o)) for o in Waypoint.objects.filter(user=user)]
self.fields['waypoints'].choices = waypoint_choices
This approach also works with a ModelChoiceField.
This approach is superior if you are using a ModelForm, and want to override choices of an autogenerated field.
How about passing the rider instance to the form while initializing it?
class WaypointForm(forms.Form):
def __init__(self, rider, *args, **kwargs):
super(joinTripForm, self).__init__(*args, **kwargs)
qs = rider.Waypoint_set.all()
self.fields['waypoints'] = forms.ChoiceField(choices=[(o.id, str(o)) for o in qs])
# In view:
rider = request.user
form = WaypointForm(rider)
If you need a dynamic choice field in django admin; This works for django >=2.1.
class CarAdminForm(forms.ModelForm):
class Meta:
model = Car
def __init__(self, *args, **kwargs):
super(CarForm, self).__init__(*args, **kwargs)
# Now you can make it dynamic.
choices = (
('audi', 'Audi'),
('tesla', 'Tesla')
)
self.fields.get('car_field').choices = choices
car_field = forms.ChoiceField(choices=[])
#admin.register(Car)
class CarAdmin(admin.ModelAdmin):
form = CarAdminForm
Hope this helps.
Underneath working solution with normal choice field.
my problem was that each user have their own CUSTOM choicefield options based on few conditions.
class SupportForm(BaseForm):
affiliated = ChoiceField(required=False, label='Fieldname', choices=[], widget=Select(attrs={'onchange': 'sysAdminCheck();'}))
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
grid_id = get_user_from_request(self.request)
for l in get_all_choices().filter(user=user_id):
admin = 'y' if l in self.core else 'n'
choice = (('%s_%s' % (l.name, admin)), ('%s' % l.name))
self.affiliated_choices.append(choice)
super(SupportForm, self).__init__(*args, **kwargs)
self.fields['affiliated'].choices = self.affiliated_choice
As pointed by Breedly and Liang, Ashok's solution will prevent you from getting the select value when posting the form.
One slightly different, but still imperfect, way to solve that would be:
class waypointForm(forms.Form):
def __init__(self, user, *args, **kwargs):
self.base_fields['waypoints'].choices = self._do_the_choicy_thing()
super(waypointForm, self).__init__(*args, **kwargs)
This could cause some concurrence problems, though.

splitting a ManyToManyField over multiple form fields in a ModelForm

So I have a model with a ManyToManyField called tournaments. I have a ModelForm with two tournament fields:
pay_tourns = forms.ModelMultipleChoiceField(
queryset=Tourn.objects.all().active().pay_tourns(),
widget=forms.CheckboxSelectMultiple())
rep_tourns = forms.ModelMultipleChoiceField(
queryset=Tourn.objects.all().active().rep_tourns(),
widget=forms.CheckboxSelectMultiple())
The methods after all() there are from a subclassed QuerySet. When I'm saving the form in my view I do thus:
post.tournaments = (post_form.cleaned_data.get('pay_tourns')
+ post_form.cleaned_data.get('rep_tourns'))
Anyway, this all works fine. What I can't figure out how to do is fill these form fields out when I'm loading an existing post. That is, when I pass instance=post to the form. Any ideas?
You could do something like this to the ModelForm:
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance')
if instance:
self.fields['pay_tourns'].queryset.filter(post=instance)
self.fields['rep_tourns'].queryset.filter(post=instance)
I don't see why that wouldn't work, but I'm going to test it just to make sure...
EDIT: Tested and it works.
Paolo Bergantino was on the right track, and helped me find it. This was the solution:
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance')
if instance:
self.fields['pay_tourns'].initial = [ o.id for o in instance.tournaments.all().active().pay_tourns()]
self.fields['rep_tourns'].initial = [ o.id for o in instance.tournaments.all().active().rep_tourns()]

Categories