Accessing user data in a Django form class - django

I have a form for which I'd like to use user data to filter the content of a choicefield.
Following this solution, I added all references to user in the __init__ function of my Form class:
class MyChoiceField(forms.Form):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(MyChoiceField, self).__init__(*args, **kwargs)
user_id = self.user.id
myobjects = forms.ModelChoiceField(label='',queryset = Myobject.objects.values_list('name', flat=True).exclude(name__isnull=True).filter(Q(person__isnull=True) | Q(person=user_id)).distinct(),empty_label=None)
And in the view I call it as:
def my_view(request):
my_list = MyChoiceField(user = request.user)
context = {
'my_list': my_list,
}
return render(request, 'foo/bar.html', context)
Debugging the __init__ part indicates the queryset content is correct, but in the view, my_list contains the following:<MyChoiceField bound=False, valid=Unknown, fields=()>.
Should I include something in the form class, outside of the __init__ part for this to work?

try this
class MyChoiceField(forms.Form):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(MyChoiceField, self).__init__(*args, **kwargs)
user_id = user.id

Related

initialize modelform fields with url parameters in cbv

I have a cbv create view that displays a modelform.
I want to preselect a foreignkey field which is displayed as select choice field.
My problem is that kwargs.get('building_id') in modelform returns None
class VlanCreateForm(ModelForm):
class Meta:
model = Vlan
fields = ['number','description','network','building']
def __init__(self, *args, **kwargs):
building_id = kwargs.get('building_id')
super().__init__(*args, **kwargs)
self.fields['building'].initial = building_id
building is a foreign key to buildings. If I put a constant like self.fields['building'].initial = 1 it is working
class VlanCreateView(CreateView):
model = Vlan
form_class = VlanCreateForm
and the url is
vlan/building/<int:building_id>/create
so I call it like
vlan/building/1/create
You'll need to define the building id in get_form_kwargs
class VlanCreateView(CreateView):
...
building_id=None
def dispatch(self, request, *args, **kwargs):
# Retrieves the building id from url
self.building_id=kwargs.get("building_id")
return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self, *args, **kwargs):
kwargs=super().get_form_kwargs(*args, **kwargs)
## Sends building id to the form
kwargs["building_id"]=self.building_id
return kwargs
class VlanCreateForm(ModelForm):
class Meta:
model = Vlan
fields = ['number','description','network','building']
def __init__(self, *args, **kwargs):
self.building_id = kwargs.get('building_id')
super().__init__(*args, **kwargs)
self.fields['building'].initial = self.building_id
def post_url(self):
return reverse('app_name:url_name',kwargs={'cg_id':self.building_id} )
In form post action use this post_url for submit form.
then you got the building_id in your view kwargs

Django DetailView and get request

I have a detail view with a drop-down list. The user can choose an item in the drop-down list and the information about that item should appear below it. This requires that the DetailView includes something like this:
def get_context_data(self, **kwargs):
context = super(InvoiceDetail, self).dispatch(*args, **kwargs)
request = self.request
if request.GET:
try:
invoice_selector = request.GET.get('invoice_selector', None)
invoice = Invoice.objects.get(id = int(invoice_selector) ) # either a string representing a number or 'add invoice'
context_object_model = invoice
except ValueError:
return HttpResponseRedirect(reverse('accounting:add_invoice'))
return context
How do I over-write the context_object_model? The above code does not make the change.
This is not something you should do in get_context_data. You should check for "add invoice" in the get method, and do the rest in get_object.
class MyDetailView(DetailView):
...
def get(self, request, *args, **kwargs):
self.invoice_selector = request.GET.get('invoice_selector')
if self.invoice_selector = 'add invoice':
return HttpResponseRedirect(reverse('accounting:add_invoice'))
return super().get(request, *args, **kwargs)
def get_object(self):
if self.invoice_selector:
obj = self.model.objects.get(pk=self.invoice_selector)
else:
obj = super().get_object()
return obj

django custom form is invalid due to form init

I have the following form:
class PlayerAchievementForm(forms.ModelForm):
class Meta:
model = PlayerAchievement
fields = ('achievement',)
def __init__(self, *args, **kwargs):
super(PlayerAchievementForm, self).__init__(**kwargs)
self.fields['achievement'].queryset = Achievement.objects.filter(input_type=0)
I have the following implementation in a view:
def points(request, action, target=None):
if request.method == 'POST':
if target == 'player':
form = PlayerAchievementForm(request.POST)
print form.errors
if form.is_valid():
print 'valid'
elif:
print 'invalid'
On submit, this prints invalid.
If I take out this line in the form:
def __init__(self, *args, **kwargs):
super(PlayerAchievementForm, self).__init__(**kwargs)
self.fields['achievement'].queryset = Achievement.objects.filter(input_type=0)
Then it saves without issue. What is wrong with the init?
I found the answer here: DJango form with custom __init__ not validating
I was missing:
super(PlayerAchievementForm, self).__init__(*args, **kwargs)
I had the same problem and after a lot of tries, the code that work for me is something like that:
(Notice the first instantiation of the the field at class level.)
class PlayerAchievementForm(forms.ModelForm):
achievement = Achievement.objects.filter(input_type=0)
class Meta:
model = PlayerAchievement
fields = ('achievement',)
def __init__(self, *args, **kwargs):
super(PlayerAchievementForm, self).__init__(**kwargs)
self.fields['achievement'].queryset = Achievement.objects.filter(input_type=0)

Pass data to django form's field clean method

I have form like this:
class TitlePropose(forms.Form):
title = forms.CharField(max_length=128)
code= forms.CharField(max_length=32)
def __init__(self, contest, *args, **kwargs):
super(TitlePropose, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = self.__class__.__name__.lower()
self.helper.form_action = ''
self.helper.layout = Layout(,
Field('title'),
Field('code'),
)
def clean_title(self):
if OtherModel.objects.filter(contest=contest, title=self.cleaned_data['title']).count() > 0:
raise forms.ValidationError("Title unavailable")
else:
return self.cleaned_data['title']
I try to access variable "contest" from clean_title method, without any success. I'm passing this variable in form class contructor:
#contest is just some object
new_title_form = TitlePropose(contest=contest.uuid)
Any suggestion, how can I get access 'contest' in clean_title?
This is standard Python class stuff. If you want to store an object so that other methods can access it, you make it an instance attribute by adding it to self.
def __init__(self, *args, **kwargs):
self.contest = kwargs.pop('contest')
super(TitlePropose, self).__init__(*args, **kwargs)
def clean_title(self):
if OtherModel.objects.filter(contest=self.contest, ...

Django get instance in inline form admin

Have a inline form class:
class ItemColorSelectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ItemColorSelectForm, self).__init__(*args, **kwargs)
#here i need current object
Inline class:
class ItemColorSelectInline(generic.GenericTabularInline):
model = ColorSelect
extra = 1
form = ItemColorSelectForm
Admin class
class ItemAdmin(admin.ModelAdmin):
inlines = [ItemColorInline,]
Question: how can a get current object in ItemColorSelectForm.
print kwargs return:
{'auto_id': u'id_%s', 'prefix': u'catalog-colorselect-content_type-object_id-__prefix__', 'empty_permitted': True}
Currently accepted solution is not thread safe. If you care about thread safety, never, ever assign an instance to a static class property.
Thread safe solutions are:
For Django 1.7 < 1.9 (possibly earlier versions, unclear):
from django.utils.functional import cached_property
def get_formset(self, *args, **kwargs):
FormSet = super(InlineAdmin, self).get_formset(*args, **kwargs)
class ProxyFormSet(FormSet):
def __init__(self, *args, **kwargs):
self.instance = kwargs['instance']
super(ProxyFormSet, self).__init__(*args, **kwargs)
#cached_property
def forms(self):
kwargs = {'instance': self.instance}
forms = [self._construct_form(i, **kwargs)
for i in xrange(self.total_form_count())]
return forms
return ProxyFormSet
As of Django >= 1.9 it's also possible to pass form_kwargs:
def get_formset(self, *args, **kwargs):
FormSet = super(InlineAdmin, self).get_formset(*args, **kwargs)
class ProxyFormSet(FormSet):
def __init__(self, *args, **kwargs):
form_kwargs = kwargs.pop('form_kwargs', {})
form_kwargs['instance'] = kwargs['instance']
super(ProxyFormSet, self).__init__(
*args, form_kwargs=form_kwargs, **kwargs)
return ProxyFormSet
Above solutions will make an instance kwarg available in the model form:
class InlineForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(InlineForm, self).__init__(*args, **kwargs)
print('instance', kwargs['instance'])
Solution:
Override the formset method in Inline class
def get_formset(self, request, obj=None, **kwargs):
InlineForm.obj = obj
return super(InlineAdmin, self).get_formset(request, obj, **kwargs)
To fix: currently accepted solution not safe in multi-thread mode
Arti's solution works, another better option could be:
Instead of passing the current object id into the inline form,
use the object id to create a inline form field within the get_formset().
# admin.py
class TransactionInline(admin.TabularInline):
model = Transaction
form = TransactionInlineForm
def get_formset(self, request, obj=None, **kwargs):
# comment Arti's solution
# TransactionInlineForm.project_id = obj.id
formset = super().get_formset(request, obj, **kwargs)
field = formset.form.declared_fields['purchase']
field.queryset = get_object_or_404(Project, pk=obj.id).products.all()
return formset
# forms.py
class TransactionInlineForm(ModelForm):
purchase = ModelChoiceField(queryset=None, label='Purchase', required=False)
So, there is no need to override the __init__() in form anymore, neither the current object.
works in Django 2.1.7