Django ModelChoiceField has no plus button - python

I'm making a Django app with custom users. I've outlined the key components of my problem below, missing code is denoted by '...'. My custom user model has a foreign key relationship as follows:
class MyCustomUser(models.AbstractBaseUser, models.PermissionsMixin)
...
location = models.ForeignKey(Location)
class Location(models.Model)
name = models.CharField(max_length=50, blank=True, null=True)
I've written a custom user form that includes this field as follows:
class MyCustomUserCreationForm(models.ModelForm)
...
location = forms.ModelChoiceField(Location.objects.all())
This all appears to be working correctly, however, there is no plus button to the right of the select field for location. I want to be able to add a location when I create a user, in the same way that you can add polls when creating choices in the Django tutorial. According to this question, I might not see the green plus if I don't have permission to change the model, but I am logged in as a superuser with all permissions. Any idea what I'm doing wrong?

You need to set a RelatedFieldWidgetWrapper wrapper in your model form:
The RelatedFieldWidgetWrapper (found in django.contrib.admin.widgets)
is used in the Admin pages to include the capability on a Foreign Key
control to add a new related record. (In English: puts the little green plus sign to the right of the control.)
class MyCustomUserCreationForm(models.ModelForm)
...
location = forms.ModelChoiceField(queryset=Location.objects.all())
def __init__(self, *args, **kwargs):
super(MyCustomUserCreationForm, self).__init__(*args, **kwargs)
rel = ManyToOneRel(self.instance.location.model, 'id')
self.fields['location'].widget = RelatedFieldWidgetWrapper(self.fields['location'].widget, rel, self.admin_site)
I could make a mistake in the example code, so see these posts and examples:
RelatedFieldWidgetWrapper
More RelatedFieldWidgetWrapper – My Very Own Popup
Django admin - How can I add the green plus sign for Many-to-many Field in custom admin form
How can I manually use RelatedFieldWidgetWrapper around a custom widget?
Django: override RelatedFieldWidgetWrapper

I have created method based on the answers above:
def add_related_field_wrapper(form, col_name):
rel_model = form.Meta.model
rel = rel_model._meta.get_field(col_name).rel
form.fields[col_name].widget =
RelatedFieldWidgetWrapper(form.fields[col_name].widget, rel,
admin.site, can_add_related=True, can_change_related=True)
And then calling this method from my form:
class FeatureForm(forms.ModelForm):
offer = forms.ModelChoiceField(queryset=Offer.objects.all(), required=False)
package = forms.ModelChoiceField(queryset=Package.objects.all(), required=False)
def __init__(self, *args, **kwargs):
super(FeatureForm, self).__init__(*args, **kwargs)
add_related_field_wrapper(self, 'offer')
add_related_field_wrapper(self, 'package')
That works fine on Django 1.8.2.

Google pointed me to this page when searching how to get a "+" icon next to fields in a custom form with ForeignKey relationship, so I thought I'd add.
For me, using django-autocomplete-light did the trick very well, using the "add another" functionality.

You don't even need to go that far, and besides, these answers are probably outdated as NONE of them worked for me in any capacity.
What I did to solve this is, as long as you have the ForeignKey field already in your model, then you can just create your custom ModelChoiceField:
class LocationModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%" % (obj.name)
The key next is NOT to create a custom field for the ModelChoiceField in your ModelForm (ie location = forms.ModelChoiceField(Location.objects.all()))
In other words, leave that out and in your ModelForm have something like this:
class UserAdminForm(forms.ModelForm):
class Meta:
model = User
fields = '__all__'
Lastly, in your ModelAdmin:
class UserAdmin(admin.ModelAdmin):
model = User
form = UserAdminForm
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'location':
return LocationModelChoiceField(queryset=Location.objects.order_by('name')) # if you want to alphabetize your query
return super().formfield_for_foreignkey(db_field, request, **kwargs)

Alternative Method : Using .remote_field instead of rel
def add_related_field_wrapper(self,form, col_name):
rel_model = form.Meta.model
rel = rel_model._meta.get_field(col_name).remote_field
form.fields[col_name].widget = RelatedFieldWidgetWrapper(form.fields[col_name].widget, rel, admin.site, can_add_related=True, can_change_related=True)
def __init__(self, *args, **kwargs):
super(CustomerAdminForm, self).__init__(*args, **kwargs)
self.add_related_field_wrapper(self, 'offer')
self.add_related_field_wrapper(self, 'package')
Thankyou,

Related

django Admin - Filter foreign key select depending on other choice in edit form (without jQuery)

I am working on a project which is administered by a super admin who puts in data for different companies.
Lets say, I have these models:
class Company(models.Model):
name = models.CharField(max_length=100)
class ContactPerson(models.Model):
name = models.CharField(max_length=100)
company = models.ForeignKey(Company)
class Item(models.Model):
company = models.ForeignKey(Company)
contact_person = models.ForeignKey(ContactPerson)
I need to ensure that I (in django admin) in the edit mode I only see contact persons which belong to the selected company.
Being not in the year 2005 anymore I want to avoid writing loads of super ugly jQuery code.
I guess I could overwrite the admin form for Item. But still I had to make the contact_person optional, so when I create a new Item, the list of contact persons need to be empty. Then I'd select a company, save it and go back to edit. Now the contact_person list would be filled and I could add somebody. But if I now change the comany, I'd have to remove all selected contact persons. Sure, I could to this in the form... but it looks SO hacky and not like a nice django solution.
Anybody got some fancy ideas?
Actually, django provided me with a neat solution.
When you look at the UserAdmin class within the django code, you'll find a built-in way to handle a two-step creation process.
#admin.register(User)
class UserAdmin(admin.ModelAdmin):
...
add_form = UserCreationForm
...
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during user creation
"""
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
When the attribute add_form is set and the object has no id yet (= we are creating it), it takes a different form than usual.
I wrapped this idea in an admin mixin like this:
class AdminCreateFormMixin:
"""
Mixin to easily use a different form for the create case (in comparison to "edit") in the django admin
Logic copied from `django.contrib.auth.admin.UserAdmin`
"""
add_form = None
def get_form(self, request, obj=None, **kwargs):
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
Now, when I have dependent fields, I create a small form, containing all values independent of - in my case - company and a regular form containing everything.
#admin.register(Item)
class ItemAdmin(AdminCreateFormMixin, admin.ModelAdmin):
form = ItemEditForm
add_form = ItemAddForm
...
Now I can customise the querysets of the dependent field in my edit form:
class ItemEditForm(forms.ModelForm):
class Meta:
model = Item
exclude = ()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['contact_person'].queryset = ContactPerson.objects.filter(company=self.instance.company)
The only drawback is, that all dependent fields need to be nullable for the database. Otherwise you wouldn't be able to save it in the creation process.
Luckily, you can tell django that a field is required in the form but not on database level with blank=False, null=True in the model declaration.
Hope this helps somebody else as well!

Django: OneToOne dropdown in the admin interface and unique associations

Referring to Django - one-to-one modelAdmin i am still searching for a solution to my problem with the admin interface of Django and my OneToOne relationship.
I have the following model which extends the standard User model with an additional attribute is_thing_staff:
class ThingStaff(models.Model):
""" Extends the django user model by a separate model relationship which holds additional user
attributes
"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
# by default a new user is not a staff member which take care of the thing administration
is_thing_staff = models.BooleanField(default=False)
def __str__(self):
return u"{}".format(self.user.username)
class Meta:
verbose_name = "Thing Staff"
verbose_name_plural = "Thing Staff"
If i create a new ThingStaff object in the django admin interface, i can select all users, even if there is already a relationship for a user. Saving a new object with a duplicate association to a user results in an error, that there is already an ThingStaff object associated with that User. So far this is more or less ok.
But why show up possible selections if they would result in an error in the next step? So i excluded them via
from django import forms
from django.contrib import admin
from .models import ThingStaff
class ThingStaffForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ThingStaffForm, self).__init__(*args, **kwargs)
self.fields['user'].queryset = User.objects.exclude(
id__in=ThingStaff.objects.values_list('user_id', flat=True)
)
#admin.register(ThingStaff)
class ThingStaffAdmin(admin.ModelAdmin):
form = ThingStaffForm
Great so far: The already associated users will not show up in the dropdown during the creation of a new ThingStaff object.
But if i want to change an existing association, the related user will also not show up in the dropdown which makes it impossible to reset the is_thing_staff flag.
So my question is: How can i enable this specific user again for the change view in the django admin interface?
Django's ModelForm distinguishes between add and change views (each one has it's on own method). This means that you can override it:
class ThingStaffAdmin(ModelAdmin):
def add_view(self, *args, **kwargs):
self.form = ThingStaffAddForm
return super().add_view(*args, **kwargs)
def change_view(self, *args, **kwargs):
self.form = ThingStaffChangeForm
return super().change_view(*args, **kwargs)
More in the docs:
https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.change_view
your exclution list must be updated and selected user for this ThingStaff must not excluded
update your form like this
class ThingStaffForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ThingStaffForm, self).__init__(*args, **kwargs)
exclude_user = ThingStaff.objects.all()
if self.instance:
exclude_user = exclude_user.exclude(pk=self.instance.pk)
self.fields['user'].queryset = User.objects.exclude(id__in=exclude_user.values('user_id'))
this code check if current form is edit form and have an instance exclude that from exclude list.

Access current user in custom django widget

I have a custom widget used to replace the dropdown for the ForeignKey field with a list of images with radiobuttons. In my render method I need to access the current user (the user who is currently logged in), like I would in a normal view with using request.user.
I have read a lot of solutions to do this with Forms, that you should pop the user object from **kwargs in your __init__ method.
However widgets doesn't have **kwargs in their __init__ method:
def __init__(self, attrs=None):
if attrs is not None:
self.attrs = attrs.copy()
else:
self.attrs = {}
How do I access the current user within a Widget sub-class?
I found a solution by reading through the Django source: Pass the user object when setting the formfield_overrides in the custom admin.
I have 2 models: News and Image. Image contains a name field and an ImageField. The News model contains a ForeignKey which points to image:
class News(models.Model):
... bunch of news related fields
image = models.ForeignKey(Image)
Then in my admin.py I have a custom admin class for news:
class NewsAdmin(admin.ModelAdmin):
model = News
def get_form(self, request, obj=None, **kwargs):
self.formfield_overrides = {
models.ForeignKey : {'widget' : SelectForeign(user = request.user)}
}
return super(NewsAdmin, self).get_form(request, obj, **kwargs)
Then in my widget.py I have a custom widget class:
class SelectForeign(widgets.Widget):
current_user = None
def __init__(self, attrs=None, choices=(), user = None):
self.current_user = user
super(SelectForegin, self).__init__(attrs, choices)
And that's it, now my widget contains the current logged in user. It's not pretty imo, but it works.
Note:
This replaces all ForeignKey fields inside the News model. To fix this, there should be a custom ForeignKey sub-class used so that we can override that one only.
If anyone has a better solution which is cleaner, please share and I'll accept.

Modifying a Django ModelForms fields

I have a Django model, which has a foreign key owner, referring to the user who owns this object.
To let other users edit these objects, I currently use a forms.ModelForm, which works fine so far.
But I know want to let the owner and only the owner change the owner of the object he owns (what an ownage! :). Thus I tried the following:
class FolderForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
if kwargs.get("instance", False):
if user == kwargs["instance"].owner:
self._meta.fields += ("owner",)
super(FolderForm, self).__init__(*args, **kwargs)
class Meta:
model = Folder
fields = (
"name",
"description",
)
But this doesn't work, since Django uses some metaclass-magic to set the fields on the model, which seems to be done before my subclassed __init__ is called.
Anyone ever did something like this?
I like to use closure normally
def make_form(exclude_user=True):
class Form(forms.ModelForm):
class Meta:
model = Folder
exclude = ['user'] if exclude_user else None
return Form
form_cls = make_form(request.user != folder.owner)
Why don't you create two Forms:
One that excludes owner for users that don't own the data (mouthful) and do a simple if statement in your view:
if request.user == Model.owner:
form = OwnerForm
else:
form = OthersForm
Keep it as simple as possible has HUGE wins down the line.

How to set True as default value for BooleanField on Django?

I am using BooleanField in Django.
By default, the checkbox generated by it is unchecked state. I want the state to be checked by default. How do I do that?
If you're just using a vanilla form (not a ModelForm), you can set a Field initial value ( https://docs.djangoproject.com/en/2.2/ref/forms/fields/#django.forms.Field.initial ) like
class MyForm(forms.Form):
my_field = forms.BooleanField(initial=True)
If you're using a ModelForm, you can set a default value on the model field ( https://docs.djangoproject.com/en/2.2/ref/models/fields/#default ), which will apply to the resulting ModelForm, like
class MyModel(models.Model):
my_field = models.BooleanField(default=True)
Finally, if you want to dynamically choose at runtime whether or not your field will be selected by default, you can use the initial parameter to the form when you initialize it:
form = MyForm(initial={'my_field':True})
from django.db import models
class Foo(models.Model):
any_field = models.BooleanField(default=True)
I am using django==1.11. The answer get the most vote is actually wrong. Checking the document from django, it says:
initial -- A value to use in this Field's initial display. This value
is not used as a fallback if data isn't given.
And if you dig into the code of form validation process, you will find that, for each fields, form will call it's widget's value_from_datadict to get actual value, so this is the place where we can inject default value.
To do this for BooleanField, we can inherit from CheckboxInput, override default value_from_datadict and init function.
class CheckboxInput(forms.CheckboxInput):
def __init__(self, default=False, *args, **kwargs):
super(CheckboxInput, self).__init__(*args, **kwargs)
self.default = default
def value_from_datadict(self, data, files, name):
if name not in data:
return self.default
return super(CheckboxInput, self).value_from_datadict(data, files, name)
Then use this widget when creating BooleanField.
class ExampleForm(forms.Form):
bool_field = forms.BooleanField(widget=CheckboxInput(default=True), required=False)
In Django 3.0 the default value of a BooleanField in model.py is set like this:
class model_name(models.Model):
example_name = models.BooleanField(default=False)
I found the cleanest way of doing it is this.
Tested on Django 3.1.5
class MyForm(forms.Form):
my_boolean = forms.BooleanField(required=False, initial=True)
I found the answer here
Another way to check the default state in BooleanField is:
active = forms.BooleanField(
widget=forms.CheckboxInput(
attrs={
'checked': True
}
)
)
Both initial and default properties were not working for me, if that's your case try this:
class MyForm(forms.ModelForm):
validated = forms.BooleanField()
class Meta:
model = MyModel
fields = '__all__'
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['validated'].widget.attrs['checked'] = True
I tried to change inital of BooleanField:
def __init__(self, *args, **kwargs):
super(UserConfirmForm,self).__init__(*args, **kwargs)
self.fields['bool_field'].initial = True
but it didn't work.
My solve:
def __init__(self, *args, **kwargs):
kwargs['initial'] = {'bool_field': True}
super(UserConfirmForm,self).__init__(*args, **kwargs)
It works like:
UserConfirmForm(initial={'bool_field':True})
but we can't call form in Generic editing views.
I think this is a great alternative to a regular call form object.

Categories

Resources