Django: Filter ModelChoiceField by user - python

I have a model along with a ModelForm based on that model. The ModelForm contains a ModelMultipleChoice field, which I specify in the subclass of my ModelForm:
class TransactionForm(ModelForm):
class Meta:
model = Transaction
def __init__(self, *args, **kwargs):
super(TransactionForm, self).__init__(*args, **kwargs)
self.fields['category'] = forms.ModelChoiceField(queryset=Category.objects.filter(user=user))
As you can see, I need to filter the Category queryset by user. In other words, users should only see their own categories on the drop down. But how can I do this when user, or more specifically, request.user, is not available in a Model instance?
Edit: Adding my subclass of the CBV:
class TransUpdateView(UpdateView):
form_class = TransactionForm
model = Transaction
template_name = 'trans_form.html'
success_url='/view_trans/'
def get_context_data(self, **kwargs):
context = super(TransUpdateView, self).get_context_data(**kwargs)
context['action'] = 'update'
return context
I tried form_class = TransactionForm(user=request.user) and I'm getting a NameError saying that request was not found.

You can pass request.user to form init in view:
def some_view(request):
form = TransactionForm(user=request.user)
and add user parameter to form __init__ method (or pop it from kwargs in form):
class TransactionForm(ModelForm):
class Meta:
model = Transaction
# def __init__(self, *args, **kwargs):
# user = kwargs.pop('user', User.objects.get(pk_of_default_user))
def __init__(self, user, *args, **kwargs):
super(TransactionForm, self).__init__(*args, **kwargs)
self.fields['category'] = forms.ModelChoiceField(
queryset=Category.objects.filter(user=user))
update: in class based views you can add extra parameter to form init in get_form_kwargs:
class TransUpdateView(UpdateView):
#...
def get_form_kwargs(self):
kwargs = super(YourView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs

Related

Using UpdateModelMixin update() to update record

How can I perform a partial update within list()? I am trying to update the balance value in a Wallet record
class WalletListCreateAPIView(generics.ListCreateAPIView, mixins.UpdateModelMixin):
queryset = Wallet.objects.all()
serializer_class = WalletSerializer
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
current_user = request.user
wallets = Wallet.objects.filter(user=current_user)
balances = get_wallet_balances([wallet.address for wallet in wallets])
for wallet in wallets:
# Update wallet balance based on address
balance = balances[wallet.address]
return super().list(request, *args, **kwargs)
You just have to create a put() function inside your view, while inheriting GenericAPIView and UpdateModelMixin
class MyView(GenericAPIView, UpdateModelMixin):
serializer_class = MySerializer
queryset = MyModel.objects.all()
lookup_field = 'id'
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
You should create your serializer to contain only the fields you want to update
serializers.py:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ["field_1", "field_2", "field_3"]
Take into consideration that any field that has null=False in the model will be required and not optional
You can dodge this by adding
extra_kwargs = {"field_x": {"required": False}}
in your Meta class

How do I pass arguments to django forms?

I want to filter form fields querysets based on the user selected. Therefore, I want to pass user as argument to the form in order to filter fields querysets in the form's __init__ method. When I pass any arguments to the form I get the following error.
class UserDetailView(LoginRequiredMixin, FormMixin, DetailView):
model = TbUser
form_class = TbPeopleEntranceRightForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = TbPeopleEntranceRightForm(user=self.object)
context['form'] = form
return context
__init__() got an unexpected keyword argument 'user'
how do I pass the argument correctly, and how I get it in the __init__ form method?
Update:
class TbPeopleEntranceRightForm(forms.ModelForm):
def __init__(self, user, **kwargs):
super().__init__(**kwargs)
print(user)
# Use `self.user` here or in some other methods.
__init__() missing 1 required positional argument: 'user'
don't do it in get_context_data, it's made for something else.
Use get_form_kwargs instead:
class UserDetailView(LoginRequiredMixin, FormMixin, DetailView):
model = TbUser
form_class = TbPeopleEntranceRightForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["user"] = self.object
return kwargs
And in your forms.py:
def __init__(self, user=None, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
You need to add the parameter to the form's __init__ method:
class TbPeopleEntranceRightForm(forms.ModelForm):
...
def __init__(self, user=None, **kwargs):
super().__init__(**kwargs)
self.user = user
# Use `self.user` here or in some other methods.
Also, the correct way to then pass the user argument to the form is to override get_form_kwargs in the view, like #MojixCoder showed.

Dajngo admin ModelForm: how to get request and/or user loggin in my form to filter queryset?

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.

send parameters from django updateview to model form

in function based view it's easy to pass parameters to form instances as form = FormName(user_id=..) and do other staffs, but I'm wondering how to do this in django update view It's my view
class MedicalrecordUpdate(UpdateView):
model = MedicalRecords
form_class = EditMedicalRecord
success_url = "."
template_name = "medicalrecord/edit_medicalrecord.html"
# ?? no idea how to pass user id to EditMedicalRecord form
forms.py
class EditMedicalRecord(forms.ModelForm):
def __init__(self, *args, **kwargs):
user_id = kwargs.pop("user_id")
super(EditMedicalRecord, self).__init__(*args, **kwargs)
...
...
You need to override get_form_kwargs:
class MedicalrecordUpdate(UpdateView):
model = MedicalRecords
form_class = EditMedicalRecord
success_url = "."
template_name = "medicalrecord/edit_medicalrecord.html"
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'user_id': self.kwargs.get('user_id')}) # self.kwargs is the kwargs passed to the view
return kwargs

How to set django many-to-many field to accept null

I am working on a python/django application. In my application there are 2 tables Store and Ad. That have many to many relation.
Class Store:
ads = models.ManyToManyField(Ad, null=True, blank=True)
Class Store:
ads = models.ManyToManyField(Ad)
I have tested it with both implementations given above but when i save my store without selecting an ad it gives me error:
ads: This field is required.
How can i set ads optional here???
View:
class StoreView(FormView):
form_class = StoreForm
success_url = "/"
template_name = 'store.html'
def __init__(self):
super(StoreView, self).__init__()
self.store = None
def get_form_kwargs(self):
kwargs = super(StoreView, self).get_form_kwargs()
kwargs['current_user'] = self.request.user
if 'store_id' in self.kwargs:
self.store = Store.objects.get(id=self.kwargs['store_id'])
kwargs['instance'] = self.store
kwargs['request'] = self.request
return kwargs
def get_context_data(self, **kwargs):
context = super(StoreView, self).get_context_data(**kwargs)
context['store_info'] = self.store
return context
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(StoreView, self).dispatch(*args, **kwargs)
def form_invalid(self, form):
return super(StoreView, self).form_invalid(form)
def form_valid(self, form):
self.object = form.save()
return super(StoreView, self).form_valid(form)
Form:
class StoreForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['ads'] = forms.ModelMultipleChoiceField(
queryset=Ad.objects.filter(type=13),
widget=forms.CheckboxSelectMultiple,
label='Ads associated with this store'
)
def save(self, commit=False):
store = super(StoreForm, self).save(commit=True)
return store
class Meta:
model = Store
add required=False in definition ads field in the form. When you override a field in model form, no attributes are inherited from the model. You have to add all constraints to it like max_length, required etc.

Categories

Resources