Django-tables2 subclass error (class attributes not passed to instantiated object) - python

I'm using django-tables2 and trying to create a new DeleteColumn class:
tables.py
class DeleteColumn(tables.TemplateColumn):
def __init__(self, *args, **kwargs):
super(DeleteColumn, self).__init__(*args, **kwargs)
self.template_name='wakemeup/admin/delete_link.html'
self.verbose_name=''
class SchoolsTable(tables.Table):
test = DeleteColumn()
class Meta:
model = School
I keep getting this error, though: ValueError: A template must be provided
Am I not creating the class properly? Why doesn't the template_name value specified in the class get passed along when creating a new instance of DeleteColumn?
Can someone please point me in the right direction?

If you take a look at the source of TemplateColumn (http://django-tables2.readthedocs.io/en/latest/_modules/django_tables2/columns/templatecolumn.html) you'll see that __init__() checks for a template_column or template_name attribute and if neither is found the ValueError you mention is thrown.
Now the problem is that you set the template_name attribute after you have called super(...).__init__ in your class, thus the template_name attribute is empty!
Edited
Sorry I didn't check the source code very thouroughly, it's been written in a funny way and doesn't use the attributes. In any case, from what I see now, you'll need to override __init__ to pass the template_name parameter to the parent's init, something like this:
class DeleteColumn(tables.TemplateColumn):
def __init__(self, *args, **kwargs):
# This will pass ``template_name`` to the super().__init__ along with any args and kwargs
super(DeleteColumn, self).__init__(*args, template_name='wakemeup/admin/delete_link.html', **kwargs)
self.verbose_name=''
I hope it works now!

Related

Failure when using setattr to add field to Django form class

I'm trying to write use Django FormView and a bit of ingenuity to create a view which will allow me to get inputs from a user that will be fed to a function. I'd like the code to be reusable, so I'd like to make a view that will be able to take a target function as a parameter and automagically create a form appropriate to that function. There is more plumbing to be done, but the general idea would be:
class FormViewForFunction(FormView):
template_name = '...'
func = None
def get_form_class(self):
class _FunctionForm(forms.Form):
pass
a = inspect.getargspec(self.func)
for argname in a['args']:
setattr(_FunctionForm, argname, forms.CharField())
return _FunctionForm
The idea would be that then you could set up something in your URLConf that used FormViewForFunction.as_view(func=***insert any function you want***) and you would wind up being presented with a form that was appropriate for specifying parameters for that function. Let's not worry about what would happen on form submission. For now I'm just stuck getting the form to generate properly.
With the code above, the form doesn't wind up having any fields! What am I doing wrong?
form's fields are initialized during initialization, you should override the __init__ method and then append the fields to the self.fields dictionary
This should work:
class FormViewForFunction(FormView):
template_name = '...'
func = None
def get_form_class(self):
a = inspect.getargspec(self.func)
class _FunctionForm(forms.Form):
def __init__(self, *args, **kwargs):
super(_FunctionForm, self).__init__(*args, **kwargs)
for argname in a['args']:
self.fields[argname] = forms.CharField()
return _FunctionForm

Django __init__() got an unexpected keyword argument using CreateView

I'm trying to override the get_form() method of the CreateView. My web page has two identical forms - one for adding a "registered" team and another for adding an "unregistered" team. If an unregistered team is being added, I want to set the team_name field of the form to "Available". As you can see in my code below, I tried accomplishing this by overriding the get_form() method
class TeamCreateView(LeagueContextMixin, CreateView):
model = Team
form_class = TeamForm
template_name = "leagueapp/editleague.html"
registered = False # the correct value of registered is passed in urls.py depending on the url that gets hit
# Overwrite the get_success_url() method
def get_success_url(self):
return '/league/editleague/' + self.kwargs.get('league_id')
def get_form(self, form_class):
form_kwargs = self.get_form_kwargs()
if not self.registered:
form_kwargs['team_name'] = "Available"
return form_class(**form_kwargs)
but this gives me the error __init__() got an unexpected keyword argument 'team_name'. What am I doing wrong and/or is there a better way to go about this?
Edit:
This is my TeamForm
class TeamForm(ModelForm):
class Meta:
model = Team
fields = ['team_name', 'captain', 'registered', 'team_location', 'phone', 'email', 'team_payment']
widgets = {
'team_name':TextInput(attrs={'class':'form-control input-md'}),
'captain':TextInput(attrs={'class':'form-control input-md'}),
'phone':TextInput(attrs={'class':'form-control input-md'}),
'email':TextInput(attrs={'class':'form-control', 'type':'email'}),
'team_location':TextInput(attrs={'class':'form-control input-md'}),
'team_payment':TextInput(attrs={'class':'form-control'}),
'registered':HiddenInput(),
}
You've defined an __init__ method on TeamForm that does not allow for the argument team_name to be present which is why you get the TypeError when you unpack form_kwargs into that __init__(). Either update the __init__ to accept the new kwarg or rewrite the __init__ to
class TeamForm(forms.Form):
def __init__(self, *args, **kwargs):
#code
You don't pass values to form initialization like that. It sounds like what you want to do is to provide custom initial data for the team name, so you should be updating the initial dict:
if not self.registered:
form_kwargs.setdefault('initial', {}).update(name="Available")
Finally figured out that I could change the get_form method like so:
def get_form(self, form_class):
if self.registered:
myform = super().get_form(form_class)
else:
myform = TeamForm({'team_name':'Available', 'team_payment':0.00, 'registered':False})
return myform

Understanding django Form initialisation

I have a class like so:
class EmailForm(forms.Form):
users = forms.MultipleChoiceField(required=False, widget=MultipleHiddenInput())
subject = forms.CharField(max_length=100)
message = forms.Textarea()
def __init__(self, users, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
self.users.choices = users
# self.fields['users'].choices = []
The commented line at the bottom works perfectly if I use it instead of self.users.
Am I right in thinking that users, subject and message are class level so that is why they are popped out of the attribute list?
So self.fields is the per object copy of the attributes in case I want to change them in some way?
Thanks.
The Form class uses the DeclarativeFieldsMetaclass, which enables the declarative syntax for the fields.
The implementation means that the form class and instance does not actually have an attribute self.field_name for each field. That is why trying to use self.users gives an error.
The fields of the form instance can be accessed as self.fields, which is created when you call super in the __init__ method.
The fields of the form class can be accessed as self.base_fields.

Django: Access and Update fields of a Form Class inside __init__ or save functions

I have a MyModelForm form class for MyModel model class, and I want to generate a random value for a certain field.
The way I see it is either inside init or save functions, I tried using self.fields['randfield'] but it throws an error 'MyModelForm' object has no attribute 'fields'.
How can I access and update a field inside form class so that I can instantiate it with a random value?
Thanks.
EDIT: After using self.fields['randint'].initial I am getting a KeyError. The code is
Okay, here goes:
def __init__(self, instance=None, *args, **kwargs):
_fields = ('username', 'email')
_initial = model_to_dict(instance.user, _fields) if instance is not None else {}
super(UserDetailsForm, self).__init__(initial=_initial, instance=instance, *args, **kwargs)
self.fields.update(fields_for_model(User, _fields))
self.fields['randint'].initial = '987654321'
Use something like this:
class RandomValueForm(ModelForm):
myfield = models.IntegerField(default=0)
def __init__(self, *args, **kwargs):
super(RandomValueForm, self).__init__(*args, **kwargs)
self.fields['myfield'].initial = my_random_generator()
You got this error because you would have tried accessing fields on self without calling the __init__ of superclass. So, first you need to call superclass __init__ i.e __init__ of ModelForm and then you can access fields.
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['myfield'].initial = my_random_number()

Why are form field __init__ methods being called on Django startup?

I have a Django app with custom form fields, some of which have slow operations in their constructors. I was surprised recently to find out that those constructors were getting called when Django itself was starting up, even before a user does something that requires that form in a view.
Why are they getting instantiated at server start?
Example:
urls.py:
from myapp.views import view1
...
url(r'^test$', view1.test),
views/view1.py:
class MyForm(ModelForm):
class Meta:
model = MyModel
field1 = MyChoiceField()
class MyChoiceField(ChoiceField):
def __init__(self, choices=(), required=True, widget=None, label=None,
initial=None, help_text=None, *args, **kwargs):
super(ChoiceField, self).__init__(required, widget, label, initial,
help_text, *args, **kwargs)
self.choices = [(m.id, m.name) for m in ReallyLargeTableModel.objects.all()]
If I set a break point inside that field constructor, then start up Django, it breaks the first time I request any page, even if the view in question does not need that form or field. The stacktrace leads back to the import line in urls.py.
Is this because I'm importing view1 in urls.py, instead of importing view1.test?
Edit: This isn't Django specific, here is a test case the illustrates the behavior:
class Something():
def __init__(self):
print "Something __init__() called"
class UsesSomething():
field = Something()
If you run this in the interactive terminal, it will print "Something init() called". This was surprising to me because I have not actually instantiated a UsesSomething object.
Because you instantiate the fields in the form definition, which is presumably being imported by one of your views.
The field init is the wrong place to do this sort of dynamic initialization, for this exact reason. You want something that is called when the form is initialized: ie, the form's __init__.
That said, you don't actually want to do this at all - you just need to use forms.ModelChoiceField, which takes a queryset and does the dynamic assignment of choices for you.
class MyForm(ModelForm):
field1 = forms.ModelChoiceField(queryset=ReallyLargeTableModel.objects.all())
In your example:
class UsesSomething():
field = Something()
The line of code field = Something() will execute when you import the containing module as Python processes the class definition. This is just how Python works. You can actually put arbitrary code inside a class definition.
module: test.py:
class UsesSomething():
print "wow!"
>>> import test
wow!

Categories

Resources