I am creating my form in Form.py like this:
class pdftabelModelForm(forms.ModelForm):
class Meta:
model = pdftabel_tool_
fields = ['apn', 'owner_name']
apn = forms.ModelChoiceField(queryset= Field.objects.values_list('name', flat=True), empty_label="(Choose field)")
owner_name = forms.ModelChoiceField(queryset= Field.objects.values_list('name', flat=True), empty_label="(Choose field)")
But due to some reasons like 'self' is not available in form.py. I can only access it in views.py. So I want to make it like
class FieldForm(ModelForm):
class Meta:
model = pdftabel_tool_
fields = (
'apn',
'owner_name',)
How can I make these fields as dropdown like I did in my forms.py?
Why are you set on doing it in views.py? forms.py is the appropriate place to do this.
Instead of redefining your fields, you should use the form's __init__ method to override the querysets for your fields, like so:
class pdftabelModelForm(forms.ModelForm):
class Meta:
model = pdftabel_tool_
fields = ['apn', 'owner_name']
def __init__(self, *args, **kwargs):
super(pdftabelModelForm, self).__init__(*args, **kwargs)
self.fields['apn'].queryset = X
self.fields['owner_name'].queryset = X
EDIT: if you need to pass extra parameters to your form, update the init method to this:
def __init__(self, *args, **kwargs):
self.layer_id = self.kwargs.pop('layer_id')
super(pdftabelModelForm, self).__init__(*args, **kwargs)
self.fields['apn'].queryset = X
self.fields['owner_name'].queryset = X
And when you initialize your form from views.py, pass the parameter:
form = pdftableModelForm(layer_id=X)
Related
I need to pass fields that are present in serializer, but not present in model to model save method (I have complicated saving logic and I want to make some decisions in object creation based on these fields). How can I do that? I tried to add
non_db_field = property to model, but I still get error MyModel() got an unexpected keyword argument 'negative_amount'
Let's say my model is
class MyModel(AbstractModel):
field1 = models.DateTimeField()
field2 = models.BigIntegerField()
My serializer is
class MyModelSerializer(AbstractSerializer):
field3 = serializers.BooleanField(required=False)
class Meta(AbstractSerializer.Meta):
model = MyModel
fields = '__all__'
And my viewset is
class MyModelViewSet(AbstractViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
You should handle this behavior in serializer.save method, for example, you can pop it from validated_data like that:
def save(self, **kwargs):
self.validated_data.pop("negative_amount")
return super().save(**kwargs)
You can use fields=['field1', 'field2', 'field3'] in serializer instead of fields='__all__'.
I found a solution based partly on Sharpek's answer and partly based on this answer:
In serializer I override save method:
def save(self, **kwargs):
if 'field3' in self.validated_data:
kwargs['field3'] = self.validated_data.pop('field3')
return super().save(**kwargs)
In models I override init method and define field:
field3 = None
def __init__(self, *args, **kwargs):
if 'field3' in kwargs:
self.field3 = kwargs.pop('field3')
super(Reading, self).__init__(*args, **kwargs)
I have a custom User class with a property that return a queryset
And I have an Admin class that use a custom ModelForm with 2 ModelChoiceField and fone BooleanFields.
I want to filter queryset of one ModelChoiceField using user property
but my problem is that I do not have access to request or user in my ModelForm.
I try to use method get_form_kwargs I use for 'normal CBV' but it doen't work as this method do not exist in ModelAdmin
admin.py
class User_TableAdmin(SimpleHistoryAdmin):
def __init__(self, model, admin_site):
super(User_TableAdmin,self).__init__(model,admin_site)
self.form.admin_site = admin_site # capture the admin_site
form = User_TableAdminForm **# How to request object to my form?**
list_display = ('id','user','table','can_download')
search_fields = ('user','table','can_download')
forms.py
class User_TableAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(User_TableAdminForm, self).__init__(*args, **kwargs)
# add the 'green +' button to create a new user (green + button suppress when overidding field with ModelChoiceField)
self.fields['user'].widget = RelatedFieldWidgetWrapper(
self.fields['user'].widget,
self.instance._meta.get_field('user').remote_field,
admin_site)
class Meta:
model = User_Table
fields = '__all__'
# display only tables of study database that user workin: User property
# tables = self.user.can_download
tables = Table.objects.all() **#<- I would like to use something like request.user.can_download**
user = forms.ModelChoiceField(queryset = User.objects.all(), label = "User", widget = forms.Select())
table = forms.ModelChoiceField(queryset = tables, label = "Table", widget = forms.Select())
can_download = forms.BooleanField(
widget = forms.CheckboxInput(),
required = False,
)
models.py
class User(AbstractUser):
# site = models.ForeignKey(Site, on_delete = models.CASCADE, related_name="database")
birth_date = models.DateField(null=True, blank=True)
#property
def can_download(self):
""" Return the related list of tables use can download. """
return Table.objects.filter(
Q(database__study__in = [uss.study.id for uss in User_Site_Study.objects.filter(user = self.id)]) &
Q(database__study__is_opened = True) &
Q(database__is_opened = True)
)
can_download.fget.short_description = 'List of tables user allowed to download'
For that you can override ModelAdmin.get_form() which returns the ModelForm class that will be used in add or change admin page and decorate it to inject request upon creation of form instance.
class ModelFormWithRequest(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super().__init__(*args, **kwargs)
#classmethod
def inject_request(cls, request):
def __new__(_, *args, **kwargs):
kwargs.setdefault('request', request)
return cls(*args, **kwargs)
return type(
f'{cls.__name__}Decorator',
(cls,),
{
'__module__': cls.__module__,
'__doc__': cls.__doc__,
'__new__': __new__
}
)
class User_TableAdminForm(ModelFormWithRequest):
def __init__(self, *args, **kwargs):
super(ModelFormWithRequest, self).__init__(*args, **kwargs)
self.fields['table'].queryset = # set new queryset filtered with self.request.user data
# add the 'green +' button to create a new user (green + button suppress when overidding field with ModelChoiceField)
self.fields['user'].widget = RelatedFieldWidgetWrapper(
self.fields['user'].widget,
self.instance._meta.get_field('user').remote_field,
admin_site)
class User_TableAdmin(SimpleHistoryAdmin):
form = User_TableAdminForm
list_display = ('id','user','table','can_download')
search_fields = ('user','table','can_download')
def __init__(self, model, admin_site):
super(User_TableAdmin,self).__init__(model,admin_site)
self.form.admin_site = admin_site # capture the admin_site
# this is how you pass request to form
def get_form(self, request, obj=None, change=False, **kwargs):
ModelForm = super().get_form(request, obj=obj, change=change, **kwargs))
# pass request only to change form, it assumes this is a subclass of auth.UserAdmin
if change:
return ModelForm.inject_request(request)
return ModelForm
just to extend answer, if one needs to pass request to InlineModelAdmin's forms, which is handled by formsets, need to override get_formset() instead of get_form()
class CustomInlineModelAdmin(admin.InlineModelAdmin):
def get_formset(self, request, obj=None, **kwargs):
formset_class = super().get_formset(request, obj=obj, **kwargs)
formset_class.form = formset_class.form.inject_request(request)
return formset_class
There is a shorter solution, just set ModelForm.request = request in ModelAdmin.get_form(). In this case ModelFormWithRequest is not needed, but I prefer to pass dynamic dependencies though constructor instead of appending it to class objects which are global.
Different proxy models should be different in type.
If I query those models I the right ones.
I am trying to save a default type field in a proxy model.
I don't want to set it everytime in the view.
This does not work. The type field is always "TYPE1".
models.py:
class MyModel(models.Model):
class ModelType(models.TextChoices):
TYPE1 = 'TYPE1', _('TYPE1')
TYPE2 = 'TYPE2', _('TYPE2')
type = models.CharField(max_length=100, choices=ModelType.choices, default='TYPE1')
class Type2Manager(models.Manager):
def get_queryset(self):
return super(Type2Manager, self).get_queryset().filter(type='TYPE2')
def save(self, *args, **kwargs):
kwargs.update({'type': 'TYPE2'})
return super(Type2Manager, self).save(*args, **kwargs)
class Type2ProxyModel(MyModel):
class Meta:
proxy = True
objects = Type2Manager()
views.py:
def create_type2_model(request):
form = Type2Form(request.POST, initial={})
f = form.save(commit=False)
f.save()
forms.py:
class Type2Form(ModelForm):
class Meta:
model = Type2ProxyModel
Update 25.02.2020 12:18:
I found out that this sets the correct type. But I don't know how to use create() in a ModelForm.
class Type2Manager(models.Manager):
...
def create(self, **kwargs):
kwargs.update({'type': 'TYPE2'})
return super(Type2Manager, self).create(**kwargs)
Type2ProxyModel.objects.create()
A model manager operates on a "table-level". When you create an object via a form it uses the model objects and not the model manager and thus you'd need to override the save of your proxy model. If I modify your Type2ProxyModel to this it works:
class Type2ProxyModel(MyModel):
class Meta:
proxy = True
objects = Type2Manager()
def save(self, *args, **kwargs):
self.type = 'TYPE2'
return super(Type2ProxyModel, self).save(*args, **kwargs)
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)
I have following form:
class EmailPreferences(forms.ModelForm):
""" Base form used for fields that are always required """
def __init__(self, *args, **kw):
super(EmailPreferences, self).__init__(*args, **kw)
class Meta:
model = teacher_email_preferences
exclude = ['id', 'c_user']
def save(self, force_insert=False, force_update=False, commit=True):
obj = super(EmailPreferences, self).save(commit=commit)
return obj
As you can see model name is fixed which is teacher_email_preferences. But my site has two types of user one is Teacher other is Student. I do not want to create a separate form. So i want to change model to student_email_preferences when initiating this form. How can I pass a model here when doing form = EmailPreferences()?
You can't. But you can use a closure instead.
def emailform(emailmodel):
class EmailPreferences(forms.ModelForm):
...
class Meta:
model = emailmodel
...
return EmailPreferences
...
form = emailform(teacher_email_preferences)()