Django form: name 'self' is not defined - django

I have a form in Django that looks like this
class FooForm(forms.ModelForm):
foo_field = forms.ModelChoiceField(widget=FooWidget(def_arg=self.data))
Where I call self.data, Python throws the exception name 'self' is not defined. How can I access self there?

As others have answered, there is no self to refer to at that point. Something like this does work though:
class FooForm(forms.ModelForm):
foo_field = forms.ModelChoiceField()
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['foo_field'].initial = self.data
You can also access the widget in __init__ through self.fields['foo_field'].widget

you can't
at the time the class is created, there is no object instance. for this kind of dynamic behaviour, you need to override the __init__ method and create the field (or change some of its parameters) there

You can't; there is no self there. You'll need to do additional setup in __init__().

Related

What *args, **kwargs doing in this save method overriding

I'm new to Django. I understand what are the usage of *args and **kwargs. And also know how to use them in method overriding.
But, I don't understand what purpose they serve while overriding the save() method in a model class.
My observation is that no number of arguments, either non-keyworded or keyworded, were assigned to them anywhere. Still why do I must use them and how.
Have this example:
class DemoModel(models.Model):
title = models.CharField(max_length = 200)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(DemoModel, self).save(*args, **kwargs)
Please explain.
From Django model documentation:
It’s also important that you pass through the arguments that can be
passed to the model method – that’s what the *args, **kwargs bit does.
Django will, from time to time, extend the capabilities of built-in
model methods, adding new arguments. If you use *args, **kwargs in
your method definitions, you are guaranteed that your code will
automatically support those arguments when they are added.
Very late but,
You should add return to the save method.
return super(DemoModel, self).save(*args, **kwargs)
Secondly, the kwargs which means keyword arguments are also URL parameters that are passed to the views like kwargs={"pk": self.object.id} when you save the method and it needs to redirect to a detail view for example, it needs the id of the just created object. The magic happens already in Django views, but you can pass extra parameters if you want.

Is save() called implicitly when calling create in django?

I'm trying to perform some custom validation on a model and I'm getting confused. Let me be specific. Let's say my code is as follows:
class FooManager(models.Manager):
def create_foo(self, name):
return self.create(foo_name = name)
class Foo(models.Model):
foo_name = models.CharField(max_length=30)
objects = FooManager()
def clean(self):
...
def save(self, *args, **kwargs):
self.full_clean()
super(User, self).save(*args, **kwargs)
Now, when I am working with this model from the shell, if I call:
f = Foo.objects.create_foo("")
It will raise a validation error before I get a chance to call save() on f. Why does this happen? Shouldn't the validation error only be raised once I call f.save()?
Note: the same thing happens if I use objects.create() as opposed to the custom defined create method. Any help would be greatly appreciated, as I'm finding validations in django to be fairly frustrating.
create() will automatically save, so even if you fix your error - you will still have to make sure the arguments to create fulfill the database requirements to save a record.
You forgot to put self in your manager
class FooManager(models.Manager):
def create_foo(self, name):
return self.create(foo_name = name)

Test if Django ModelForm has instance

I would like to display a warning message if I am into an editing form and hide it if I am in a creation form of a Django ModelForm.
form.is_bound tell me if the form was previously populated but how to test if the ModelForm was set with an existing instance ?
I tried this hasattr(form.instance, 'pk') but is it the right way to do so ?
Cheers,
Natim
Try checking if form.instance.pk is None.
hasattr(form.instance, 'pk') will always return True, because every model instance has a pk field, even when it has not yet been saved to the database.
As pointed out by #Paullo in the comments, this will not work if you manually define your primary key and specify a default, e.g. default=uuid.uuid4.
Since the existed instance would be passed as an argument with the keyword instance to create the model-form, you can observe this in your custom initializer.
class Foo(ModelForm):
_newly_created: bool
def __init__(self, *args, **kwargs):
self._newly_created = kwargs.get('instance') is None
super().__init__(*args, **kwargs)
I encountered this issue but in my case, am using UUID for PK. Though the accepted answer is correct for most cases but fails if you are not using Django default auto increment PK.
Defining a model property gives me the ability to access this value from both, Model, View and Template as attribute of the model
#property
def from_database(self):
return not self._state.adding
I found that self.instance is set in super().init anyway
class BaseModelForm(BaseForm):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, instance=None, use_required_attribute=None,
renderer=None):
...
if instance is None:
# if we didn't get an instance, instantiate a new one
self.instance = opts.model()
https://github.com/django/django/blob/65e03a424e82e157b4513cdebb500891f5c78363/django/forms/models.py#L300
so we can track instance just before super().init called.
So my solution is to override init method and set custom field to track in all followed form's methods.
def __init__(self, *args: Any, instance=None, **kwargs: Any) -> None:
super().__init__(*args, instance=instance, **kwargs)
self.is_new_instance = not bool(instance)
and usage:
def _any_form_method(self):
if self.is_new_instance:

Django: parametric class-based views

I am trying to use generic CreateView class to handle forms for a set of models inherited from the same base class.
class BaseContent(models.Model):
...
class XContent(BaseContent):
...
class YContent(BaseContent):
...
To keep things DRY, I want to define one CreateView class that will handle all inherited classes from BaseContent.
The url pattern for that view is:
url(r'^content/add/(?P<model_name>\w+)/$', ContentCreateView.as_view(), name='content_add')
Something like this should work:
class ContentCreateView(CreateView):
template_name = 'content_form.html'
def get_model(self, request):
# 'content' is the name of the application; model_name is 'xcontent', 'ycontent', ...
return ContentType.objects.get_by_natural_key('content', self.model_name)
But I am getting this exception:
ContentCreateView is missing a queryset. Define ContentCreateView.model, ContentCreateView.queryset, or override ContentCreateView.get_object().
This suggestion does not seem to hold as I am not willing to set a class attribute like model or queryset to keep the model form generated dynamic. Overriding the get_object does not seem relevant for creating an object.
I tried overriding get_queryset() but this method does not accept the request parameter, nor have access to self.model_name which comes from the url pattern.
Long story short, how can I make a CreateView use a dynamic form based on a parameter passed from the url?
Thanks.
You could set the model attribute from your your urls.py, depending on the url being called:
url(r'^content/add/x/$',
ContentCreateView.as_view(model=XContent), name='x_content_add'),
url(r'^content/add/y/$',
ContentCreateView.as_view(model=YContent), name='y_content_add')
I admit it's not perfect as you are repeating yourself a bit, but therefore you have the advantage of having different names for the same view, depending on the model! Besides that you could also do something similar with overriding form_class...
Had this problem for some time, but found the solution. You need to override the dispatch method, defined in as_view() (django.views.generic.base), something like this:
class ContentCreateView(CreateView):
def dispatch(self, request, *args, **kwargs):
for app in ['foo', 'bar']:
model = models.get_model(app, kwargs['modelname'])
if model:
self.model = model
break
return super(GenericEdit, self).dispatch(request, *args, **kwargs)
...
...

adding new form fields dynamically in admin

I am trying to add dynamically new form fields (I used this blog post), for a form used in admin interface :
class ServiceRoleAssignmentForm(forms.ModelForm):
class Meta:
model = ServiceRoleAssignment
def __init__(self, *args, **kwargs):
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)
self.fields['test'] = forms.CharField(label='test')
class ServiceRoleAssignmentAdmin(admin.ModelAdmin):
form = ServiceRoleAssignmentForm
admin.site.register(ServiceRoleAssignment, ServiceRoleAssignmentAdmin)
However, no matter what I try, the field doesn't appear on my admin form ! Could it be a problem related to the way admin works ? Or to ModelForm ?
Thank for any help !
Sébastien
PS : I am using django 1.3
When rendering your form in template, fields enumerating from fieldsets variable, not from fields. Sure you can redefine fieldsets in your AdminForm, but then validations will fail as original form class doesn't have such field. One workaround I can propose is to define this field in form definition statically and then redefine that field in form's init method dynamically. Here is an example:
class ServiceRoleAssignmentForm(forms.ModelForm):
test = forms.Field()
class Meta:
model = ServiceRoleAssignment
def __init__(self, *args, **kwargs):
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)
# Here we will redefine our test field.
self.fields['test'] = forms.CharField(label='test2')
I actually have a the same issue which I'm working through at the moment.
While not ideal, I have found a temporary workaround that works for my use case. It might be of use to you?
In my case I have a static name for the field, so I just declared it in my ModelForm. as normal, I then override the init() as normal to override some options.
ie:
def statemachine_form(for_model=None):
"""
Factory function to create a special case form
"""
class _StateMachineBaseModelForm(forms.ModelForm):
_sm_action = forms.ChoiceField(choices=[], label="Take Action")
class Meta:
model = for_model
def __init__(self, *args, **kwargs):
super(_StateMachineBaseModelForm, self).__init__(*args, **kwargs)
actions = (('', '-----------'),)
for action in self.instance.sm_state_actions():
actions += ((action, action),)
self.fields['_sm_action'] = forms.ChoiceField(choices=actions,
label="Take Action")
if for_model: return _StateMachineBaseModelForm
class ContentItemAdmin(admin.ModelAdmin):
form = statemachine_form(for_model=ContentItem)
Now as I mentioned before, this is not entirely 'dynamic', but this will do for me for the time being.
I have the exact same problem that, if I add the field dynamically, without declaring it first, then it doesn't actually exist. I think this does in fact have something to do with the way that ModelForm creates the fields.
I'm hoping someone else can give us some more info.
Django - Overriding get_form to customize admin forms based on request
Try to add the field before calling the super.init:
def __init__(self, *args, **kwargs):
self.fields['test'] = forms.CharField(label='test')
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)