note : This is closely related to the answer in this question :
django admin - add custom form fields that are not part of the model
In Django it is possible to create custom ModelForms that have "rouge" fields that don't pertain to a specific database field in any model.
In the following code example there is a custom field that called 'extra_field'. It appears in the admin page for it's model instance and it can be accessed in the save method but there does not appear to be a 'load' method.
How do I load the 'extra_field' with data before the admin page loads?
# admin.py
class YourModelForm(forms.ModelForm):
extra_field = forms.CharField()
def load(..., obj):
# This method doesn't exist.
# extra_field = obj.id * random()
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
return super(YourModelForm, self).save(commit=commit)
class Meta:
model = YourModel
class YourModelAdmin(admin.ModelAdmin):
form = YourModelForm
fieldsets = (
(None, {
'fields': ('name', 'description', 'extra_field',),
}),
)
source code by #vishnu
Override the form's __init__ method and set the initial property of the field:
class YourModelForm(forms.ModelForm):
extra_field = forms.CharField()
def __init__(self, *args, **kwargs):
super(YourModelForm, self).__init__(*args, **kwargs)
initial = '%s*rnd' % self.instance.pk if self.instance.pk else 'new'
self.fields['extra_field'].initial = initial
Related
I have a custom ModelForm class and ModelAdmin class and for some reason my save_model method will not execute.
Ill show both classes i currently have in admin.py. all functions (search, filter, hiding fields, delete) currently work correctly except for saving a new entry.
Here is my form:
class SeasonalityOriginalsForm(forms.ModelForm):
# defining the input fields that should be hidden
class Meta:
model = SeasonalitiesCalculated
fields = '__all__'
widgets = {
'has_been_reviewed': forms.HiddenInput(),
'user': forms.HiddenInput(),
'source_importance_0least_to_10most': forms.HiddenInput(),
'internal_id': forms.HiddenInput(),
}
### this defines a dropdown selector field for object_id
### instead of copy-pasting the UUID from another adminmodel page, which could bring errors
### admins can now select the country or admin_zone_1 with the right granularity by selecting location name
### an issue is that this is a choicefield, not a model choicefield.
### this issue is solved in the save_model method in the ModelAdmin class.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
logger.debug(f'self model info: {type(self.fields)}')
countries = Countries.objects.filter(level_of_seasonal_area_granularity='country')
admin_zones1 = AdminZones1.objects.filter(country__level_of_seasonal_area_granularity='admin_1')
choices = [(obj.public_id, str(obj)) for obj in countries] + [(obj.public_id, str(obj)) for obj in admin_zones1]
self.fields['object_id'] = forms.ChoiceField(choices=[])
self.fields['object_id'].choices = choices
logger.debug(f'self model info2: {self.fields}')
### here are the visible input fields of the
object_id = forms.ChoiceField(choices=[])
content_type = forms.ModelChoiceField(
queryset=ContentType.objects.filter(model__in=['countries', 'adminzones1']),
widget=forms.Select(attrs={'class': 'form-control'})
)
seasonality = forms.DecimalField(min_value=Decimal('0'), max_value=Decimal('1'), decimal_places=1)
Here is my ModelAdmin class:
### define SeasonalityOriginalsAdmin for ContributorAdmin
class SeasonalityOriginalsAdmin(admin.ModelAdmin):
form = SeasonalityOriginalsForm
### modifying the search method for the searchfield
def get_search_results(self, request, queryset, search_term):
#... skipping this for now
### defining that existing fields are readonly
def get_readonly_fields(self, request, obj=None):
if obj: # editing an existing object
return [field.name for field in self.model._meta.fields]
return []
### not allowing delete permissions
def has_delete_permission(self, request, obj=None):
return False
### save method modification
def save_model(self, request, obj, form, commit=True):
logger.debug(f'tried several logs and print statements here but none show')
location_name = self.cleaned_data['object_id']
location = Countries.objects.filter(country_name=location_name).first() or AdminZones1.objects.filter(admin_zone_name=location_name).first()
self.object_id = location.pk
max_internal_id = self.model.objects.filter(internal_id__isnull=False).aggregate(Max('internal_id'))['internal_id__max']
obj.internal_id = max_internal_id + 1 if max_internal_id is not None else 1
if request.user.is_contributor:
obj.source_importance_0least_to_10most = 10
else:
obj.source_importance_0least_to_10most = 3
obj.user = request.user
return super().save(commit)
### defining the displayed rows, fields and search field
list_per_page = 20
list_display = ('ingredient_original', 'content_object','seasonality')
search_fields = ('seasonality_originals__public_id', 'ingredient_original__ingredient_original_name_en',)
ordering = ('ingredient_original',)
something must be super wrong with my save method because it wont even execute any logs that I place in the codeblock.
My model itself does not have any custom save method.
this is my views.py :
i want save type in device field in model
class GetDeviceMixin( object):
def setup(self, request, *args, **kwargs):
super().setup( request, *args, **kwargs)
type= request.META['HTTP_USER_AGENT']
print(type)
return type
class RegisterView(GetDeviceMixin , generic.CreateView):
form_class = CustomUserCreationForm
success_url = reverse_lazy("register")
template_name = "man/man.html"
and this is my models.py
class account(AbstractBaseUser):
first_name= models.CharField(max_length=20,verbose_name="first name")
device = models.CharField(verbose_name="device" , max_length=100)
this is my forms.py:
class GetReq(forms.ModelForm):
class Meta:
model = account
fields = ['device',]
First, pop Classy CBVs onto your browser bookmark list ...
I'm assuming that you want to use a form, either to get other information from the user, or to allow the user to override the automatically determined value of device. In this case, you want to pass it as the initial value for device to the form
Now, look at CreateView to work out what to subclass. get_initial() looks hopeful, so
def get_initial(self):
initial = super().get_initial()
initial['device'] = self.device_type # as per the comment!
return initial
You should now see a form with the automatically determined value as the default value
If the intent was to get other fields of the model from the user and to always forcibly insert the automatically determined device_type, you would instead subclass form_valid
def form_valid(form):
obj = form.save( commit=False)
obj.device = self.device_type
obj.save()
How can I filter a queryset inside the Admin page of an object that has a ManyToManyField relation with a manually defined through model?
Given models.py
class Foo(models.Model):
foo_field1 = models.CharField(max_length=50)
class Main(models.Model):
main_field1 = models.CharField(max_length=50)
m2mfield = models.ManyToManyField(Foo, through="FooBar")
class FooBar(models.Model):
main = models.ForeignKey(Main, on_delete=models.CASCADE)
foo = models.ForeignKey(Foo, on_delete=models.CASCADE)
new_field = models.CharField(max_length=50)
Inside admin.py
class M2MInlineAdmin(admin.TabularInline):
model = Main.m2mfield.through
extra = 1
class MainAdmin(admin.ModelAdmin):
inlines = [M2MInlineAdmin,]
...
def formfield_for_manytomany(self, db_field, request, **kwargs):
print('called formfield_for_manytomany')
return super().formfield_for_manytomany(db_field, request, **kwargs)
def get_field_queryset(self, db, db_field, request):
print('called get_field_queryset')
return super().get_field_queryset(db, db_field, request)
I try to access both of these methods, but none of them are called if I specify a through table. However, they do get called if the ManyToMany relation is simply defined as like this:
class Main(models.Model):
main_field1 = models.CharField(max_length=50)
m2mfield = models.ManyToManyField(Foo)
Is there a method to filter the queryset when a through table is specified (while being able to access the request context)?
EDIT:
The methods are indeed called when the ManyToManyField has a through model specified, only if there are no fieldsets specified inside the modelAdmin class.
How to access these methods when fieldsets are defined?
formfield_for_manytomany method seems to be called only when default form is used. When fieldsets is defined, it is using a different form which is why above method is not getting called.
Since you are using tabular admin for many to many field, you can override get_queryset to filter with field.
class M2MInlineAdmin(admin.TabularInline):
model = Main.fruits.through
extra = 1
def get_queryset(self, request):
qs = super(M2MInlineAdmin, self).get_queryset(request)
qs = qs.filter(some_arg=some_value)
return qs
Alternatively, you can write a custom model form and use it in admin instead of default form.
class MainAdminForm(forms.ModelForm):
class Meta:
model = Main
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# custom setup
class MainAdmin(admin.ModelAdmin):
form = MainAdminForm
You can use the formfield_for_foreignkey() method on the inline class.
class M2MInlineAdmin(admin.TabularInline):
model = Main.m2mfield.through
extra = 1
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
Let's say I create an model MyModel instance that has a FileField using CreateView and associated MyModelCreateForm, and now I want to update it without uploading the same avatar image:
class MyModel(models.Model):
name = models.CharField()
avatar = models.ImageField()
class MyModelCreateForm(forms.ModeForm):
class Meta:
model = MyModel
exclude = None
class MyModelCreate(CreateView):
model = MyModel
form_class = MyModelCreateForm
class MyModelCreate(UpdateView):
model = MyModel
form_class = ?
On the CreateView's form the image is compulsory. If I want to make it optional on the update field, what's the most Django-ish way of doing this?
Do I need to make a new form for the UpdateView that inherits from MyModelCreateForm but overrides the ImageField required attribute? Or is there a more "batteries-included" way?
Here a solution to make a field required :
class Foo(CreateView):
model = Fiz
fields = ('bar',)
def get_form(self, form_class=None):
form = super(Foo, self).get_form(form_class)
form.fields['bar'].required = True
return form
i think no but you can implement in it !
class ModelXForm(ModelForm):
class Meta:
model = x
fields = ['image','name']
def __init__(self,update, survey, **kwargs):
super(FBRequestForm, self).__init__(**kwargs)
if not update:
self.fields['image'].widget.attrs['readonly'] = True
self.fields['image'].widget = forms.HiddenInput()
admin.py
class PromoAdmin(admin.modelAdmin)
list_display = ( 'name', 'id', 'category', 'promo_type', 'store', 'brand', 'date_start' )
form = SampleForm
forms.py
class SampleForm(forms.ModelForm):
class Meta:
model = Promo
def __init__(self, request *args, **kwargs):
super(PromoAdminForm, self).__init__(*args, **kwargs)
self.fields["store"].queryset = Store.objects.filter(onwer=request.user)
got an error on request
Django Version: 1.3.1
Exception Type: TypeError
Exception Value:
init() takes at least 2 arguments (1 given)
You cannot initiate the store field with request.user in the field declaration. You can try the following:
class MyAwesomeForm(forms.ModelForm):
store = forms.ModelChoiceField(Store.objects)
class Meta:
model = Promo
def __init__(self, user, *args, **kwargs):
super(MyAwesomeForm, self).__init__(*args, **kwargs)
self.fields['store'].queryset = Store.objects.filter(owner=user)
While instantiating the form you can pass the request.user object.
myform = MyAwesomeForm(request.user)
If you want to achieve this in the admin you might try this
For providing only the objects related to the logged-in user in the admin provides the possibility to overwrite ModelAdmin.queryset function:
class MyModelAdmin(admin.ModelAdmin):
form = MyAwesomeAdminForm()
def queryset(self, request):
qs = super(MyModelAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(store__owner=request.user)
class MyAwesomeAdminForm(forms.ModelForm):
class Meta:
model = Promo
Note that store__owner only works if you have a foreign key field stored in your promo model as such:
class Promo(models.Model):
store = models.ForeignKey(Store)
class Store(models.Model):
owner = models.ForeignKey(User)
I assume it should also be possible to somehow pass the request to the init method of the form. But did not find a suitable approach to do it.