Modify django model form field with widget - python

I want to modify some fields in model form, and i found two methods:
First Method:
class ProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['bio'].widget.attrs.update({'placeholder': 'Enter your bio here'})
class Meta:
model = Profile
fields = ['bio']
Second Method:
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['bio']
widgets = {
'bio': Textarea(attrs={'placeholder': 'Enter your bio here'})
I just want to know if they are the same?
Which is better?
Or is there another better way?
Thankyou.

Method 1
The first method calls the constructor super().__init__(*args, **kwargs) prior to manipulating the field. This enables developers to build the class in a default state and then play around with the classes components (attributes, functions).
The first method is most commonly used if the developer cannot achieve the results they want within the second method. This is because you're moving away from configuration to more of a manipulation of the class.
Method 2
The second method allows developers to define the configuration of the class before it is instantiated. This generally provides better usability and readability to other developers.
EXAMPLE:
Say you want your field bio to be a required for all general users except for superusers.
class ProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop(user, None)
super().__init__(*args, **kwargs)
if self.user.is_superuser():
self.fields['bio'].required = False
class Meta:
model = Profile
fields = ['bio']
Using this method you can allow your Profile model's bio field attributes to be transposed to a form field upon instantiating and then making a small tweak to determine whether it's required for that particular user. This can be done without redefining the whole field.
Note: The form call in the GET request would look like ProfileForm(user=request.user)

Related

How to change the fields displayed if an instance is passed to a form?

I have a form something like this:
class ExampleForm(forms.ModelForm):
class Meta:
model = Example
fields = ('field1','field2')
Now, I want to add an additional field if an instance is passed to it. Which means that, if I create an form with an instance (for editing that object) I want to change the fields being displayed.
I know I can use create an another form for this purpose and create an instance of that rather than using this. However, is there a way to do this in this same form?
Override the __init__ method, and modify self.fields when instance is passed to the form.
class ExampleForm(forms.ModelForm):
class Meta:
model = Example
fields = ('field1','field2')
def __init__(self, *args, **kwargs):
super(ExampleForm, self).__init__(*args, **kwargs)
if kwargs.get('instance'):
self.fields['field3'] = forms.CharField()

Restricting ForeignKey choices on sub-classes

I have a set of models, regarding restaurants and the chefs that run them*:
class Chef(models.Model):
name = models.TextField()
class Restaurant(models.Model):
name = models.TextField()
chef = models.ForeignKey(Chef)
class FrenchChef(Chef):
angryness = models.PositiveIntegerField()
class FrenchRestaurant(Restaurant):
region = models.TextField()
Unfortunately, this current model means a non-FrenchChef can run a FrenchRestaurant.
Is there away that I can restrict the queryset for the ForeignKey of a subbclassed model to be a subset of those available on the parent class?
* My modelling isn't actually chefs and restaurants, but this is easier to explain. It might not seem obvious, but Chefs and FrenchChefs do need to be modelled differently.
You could try defining clean method if you're concerned about that
class FrenchRestaurant(models.Model):
# ...
def clean(self):
if not isinstance(self.chief, FrenchChief):
raise ValidationError()
By doing this:
class FrenchChef(Chef):
angryness = models.PositiveIntegerField()
you are creating one more table in database besides Chef. Read about types of model inheritance here: https://docs.djangoproject.com/en/1.7/topics/db/models/#model-inheritance
I think you should create one table for chefs and one table for restaurants, no inheritance needed here:
class Chef(models.Model):
name = models.TextField()
# all chefs with not null angryness is frenchchefs...
# but you can add some field to explicitly save chef type
angryness = models.PositiveIntegerField(null=True)
class Restaurant(models.Model):
name = models.TextField()
chef = models.ForeignKey(Chef)
region = models.TextField()
# rtype added here but it is not necessarily
rtype = models.IntegerField(choices=restaurans_types)
And restriction (filtering) of choices should be in forms:
class FrenchRestaurantForm(forms.ModelForm):
def __init__(self, *args,**kwargs):
super (FrenchRestaurantForm, self ).__init__(*args,**kwargs)
self.fields['chef'].queryset = Chef.objects.filter(
angryness__gte=MIN_ANGRYNESS_LVL)
def save(commit=True):
model = super(FrenchRestaurantForm, self).save(commit=False)
model.rtype = SomeFrenchRestTypeConst
if commit:
model.save()
return model
class Meta:
model = Restaurant
To check user input you can add clean method to form field https://docs.djangoproject.com/en/1.7/ref/forms/validation/#cleaning-a-specific-field-attribute
If FrenchChef was created intentionally (it is a different table in database), then you should add it to FrenchRestaurant (another table -> another fk id):
class FrenchRestaurant(Restaurant):
region = models.TextField()
frenchchef = models.ForeignKey(FrenchChef)
Like I was mentioning in the comment, You can look at django model data validation methods. Just found another note at this post.
Adding Custom Django Model Validation
Below is a common pattern followed to do validation. code snippet is extract from one of the answers in the abouve mentioned post . answered by https://stackoverflow.com/users/247542/cerin
class BaseModel(models.Model):
def clean(self, *args, **kwargs):
# add custom validation here
super(BaseModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(BaseModel, self).save(*args, **kwargs)
You can go ahead and read more about validation in django documentation.
If you are looking for any type/inheretance based solutions that might exist. I am not sure if they might exist. I would still like to see if someone comes up with such provision in django.

Overriding a parent's form field with options?

I have a base class that is a Django form. The child class then inherits from the parent including all the fields, but I need to make changes to one of the parent's field option such as label, required, and etc.
Example:
class BaseForm(forms.Form):
userid = forms.CharField(required=True)
class ChildForm(BaseForm):
# I need to change the parent field option
userid = forms.CharField(required=False)
Any suggestions?
You're doing exactly what you should do.
It's particularly fitting in this case because that's the exact pattern for overriding ModelForm fields.
If you need to retain properties you don't know about / are outside your control (or what have you), you could override the __init__ method and access the form fields via self.fields['myfield']
class ChildForm(BaseForm):
def __init__(self, *args, **kwargs):
super(ChildForm, self).__init__(*args, **kwargs)
self.fields['userid'].required = False

Django ModelForm with User data in Generic View

I have a model with a foreign key to group (the other fields don't matter):
class Project(models.Model) :
group = models.ForeignKey(Group)
...
I have a model form for this model:
class AddProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ["group","another"]
In my urls, I am using this in a generic view:
(r'^$', create_object, {'form_class':AddProjectForm, 'template_name':"form.html", 'login_required':True, 'extra_context':{'title':'Add a Project'}}),
That all works, but I want to have the group field display only the groups that the current user belongs to, not all of the groups available. I'd normally do this by passing in the user to the model form and overriding init if I wasn't in a generic view. Is there any way to do this with the generic view or do I need to go with a regular view to pass in that value?
This is gonna look dirty, since the generic view instantiates the form_class with no parameters. If you really want to use the generic_view you're gonna have to generate the class dynamically :S
def FormForUser(user):
class TmpClass(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TmpClass, self).__init__(*args, **kwargs)
self.fields['group'].queryset = user.group_set.all()
class Meta:
model = Project
fields = ['group', 'another']
Then wrap the create object view
#login_required # Only logged users right?
def create_project(request):
user = request.user
form_class = FormForUser(user)
return create_object(request, form_class=form_class, ..... )
My recommendation is to write your own view, it will give you more control on the long term and it's a trivial view.
No, you'll need to make a regular view. As can be seen by looking at the source code for create_object(), there's no functionality to pass in extra parameters to the modelform (in django 1.2):
http://code.djangoproject.com/svn/django/branches/releases/1.2.X/django/views/generic/create_update.py

How do I restrict foreign keys choices to related objects only in django

I have a two way foreign relation similar to the following
class Parent(models.Model):
name = models.CharField(max_length=255)
favoritechild = models.ForeignKey("Child", blank=True, null=True)
class Child(models.Model):
name = models.CharField(max_length=255)
myparent = models.ForeignKey(Parent)
How do I restrict the choices for Parent.favoritechild to only children whose parent is itself? I tried
class Parent(models.Model):
name = models.CharField(max_length=255)
favoritechild = models.ForeignKey("Child", blank=True, null=True, limit_choices_to = {"myparent": "self"})
but that causes the admin interface to not list any children.
I just came across ForeignKey.limit_choices_to in the Django docs.
Not sure yet how it works, but it might be the right thing here.
Update: ForeignKey.limit_choices_to allows one to specify either a constant, a callable or a Q object to restrict the allowable choices for the key. A constant obviously is of no use here, since it knows nothing about the objects involved.
Using a callable (function or class method or any callable object) seems more promising. However, the problem of how to access the necessary information from the HttpRequest object remains. Using thread local storage may be a solution.
2. Update: Here is what has worked for me:
I created a middleware as described in the link above. It extracts one or more arguments from the request's GET part, such as "product=1", and stores this information in the thread locals.
Next there is a class method in the model that reads the thread local variable and returns a list of ids to limit the choice of a foreign key field.
#classmethod
def _product_list(cls):
"""
return a list containing the one product_id contained in the request URL,
or a query containing all valid product_ids if not id present in URL
used to limit the choice of foreign key object to those related to the current product
"""
id = threadlocals.get_current_product()
if id is not None:
return [id]
else:
return Product.objects.all().values('pk').query
It is important to return a query containing all possible ids if none was selected so that the normal admin pages work ok.
The foreign key field is then declared as:
product = models.ForeignKey(
Product,
limit_choices_to={
id__in=BaseModel._product_list,
},
)
The catch is that you have to provide the information to restrict the choices via the request. I don't see a way to access "self" here.
The 'right' way to do it is to use a custom form. From there, you can access self.instance, which is the current object. Example --
from django import forms
from django.contrib import admin
from models import *
class SupplierAdminForm(forms.ModelForm):
class Meta:
model = Supplier
fields = "__all__" # for Django 1.8+
def __init__(self, *args, **kwargs):
super(SupplierAdminForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['cat'].queryset = Cat.objects.filter(supplier=self.instance)
class SupplierAdmin(admin.ModelAdmin):
form = SupplierAdminForm
The new "right" way of doing this, at least since Django 1.1 is by overriding the AdminModel.formfield_for_foreignkey(self, db_field, request, **kwargs).
See http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
For those who don't want to follow the link below is an example function that is close for the above questions models.
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "favoritechild":
kwargs["queryset"] = Child.objects.filter(myparent=request.object_id)
return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
I'm only not sure about how to get the current object that is being edited. I expect it is actually on the self somewhere but I'm not sure.
This isn't how django works. You would only create the relation going one way.
class Parent(models.Model):
name = models.CharField(max_length=255)
class Child(models.Model):
name = models.CharField(max_length=255)
myparent = models.ForeignKey(Parent)
And if you were trying to access the children from the parent you would do
parent_object.child_set.all(). If you set a related_name in the myparent field, then that is what you would refer to it as. Ex: related_name='children', then you would do parent_object.children.all()
Read the docs http://docs.djangoproject.com/en/dev/topics/db/models/#many-to-one-relationships for more.
If you only need the limitations in the Django admin interface, this might work. I based it on this answer from another forum - although it's for ManyToMany relationships, you should be able to replace formfield_for_foreignkey for it to work. In admin.py:
class ParentAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
self.instance = obj
return super(ParentAdmin, self).get_form(request, obj=obj, **kwargs)
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name == 'favoritechild' and self.instance:
kwargs['queryset'] = Child.objects.filter(myparent=self.instance.pk)
return super(ChildAdmin, self).formfield_for_foreignkey(db_field, request=request, **kwargs)
#Ber: I have added validation to the model similar to this
class Parent(models.Model):
name = models.CharField(max_length=255)
favoritechild = models.ForeignKey("Child", blank=True, null=True)
def save(self, force_insert=False, force_update=False):
if self.favoritechild is not None and self.favoritechild.myparent.id != self.id:
raise Exception("You must select one of your own children as your favorite")
super(Parent, self).save(force_insert, force_update)
which works exactly how I want, but it would be really nice if this validation could restrict choices in the dropdown in the admin interface rather than validating after the choice.
I'm trying to do something similar. It seems like everyone saying 'you should only have a foreign key one way' has maybe misunderstood what you're trying do.
It's a shame the limit_choices_to={"myparent": "self"} you wanted to do doesn't work... that would have been clean and simple. Unfortunately the 'self' doesn't get evaluated and goes through as a plain string.
I thought maybe I could do:
class MyModel(models.Model):
def _get_self_pk(self):
return self.pk
favourite = models.ForeignKey(limit_choices_to={'myparent__pk':_get_self_pk})
But alas that gives an error because the function doesn't get passed a self arg :(
It seems like the only way is to put the logic into all the forms that use this model (ie pass a queryset in to the choices for your formfield). Which is easily done, but it'd be more DRY to have this at the model level. Your overriding the save method of the model seems a good way to prevent invalid choices getting through.
Update
See my later answer for another way https://stackoverflow.com/a/3753916/202168
Do you want to restrict the choices available in the admin interface when creating/editing a model instance?
One way to do this is validation of the model. This lets you raise an error in the admin interface if the foreign field is not the right choice.
Of course, Eric's answer is correct: You only really need one foreign key, from child to parent here.
An alternative approach would be not to have 'favouritechild' fk as a field on the Parent model.
Instead you could have an is_favourite boolean field on the Child.
This may help:
https://github.com/anentropic/django-exclusivebooleanfield
That way you'd sidestep the whole problem of ensuring Children could only be made the favourite of the Parent they belong to.
The view code would be slightly different but the filtering logic would be straightforward.
In the admin you could even have an inline for Child models that exposed the is_favourite checkbox (if you only have a few children per parent) otherwise the admin would have to be done from the Child's side.
A much simpler variation of #s29's answer:
Instead of customising the form,
You can simply restrict the choices available in form field from your view:
what worked for me was:
in forms.py:
class AddIncomingPaymentForm(forms.ModelForm):
class Meta:
model = IncomingPayment
fields = ('description', 'amount', 'income_source', 'income_category', 'bank_account')
in views.py:
def addIncomingPayment(request):
form = AddIncomingPaymentForm()
form.fields['bank_account'].queryset = BankAccount.objects.filter(profile=request.user.profile)
from django.contrib import admin
from sopin.menus.models import Restaurant, DishType
class ObjInline(admin.TabularInline):
def __init__(self, parent_model, admin_site, obj=None):
self.obj = obj
super(ObjInline, self).__init__(parent_model, admin_site)
class ObjAdmin(admin.ModelAdmin):
def get_inline_instances(self, request, obj=None):
inline_instances = []
for inline_class in self.inlines:
inline = inline_class(self.model, self.admin_site, obj)
if request:
if not (inline.has_add_permission(request) or
inline.has_change_permission(request, obj) or
inline.has_delete_permission(request, obj)):
continue
if not inline.has_add_permission(request):
inline.max_num = 0
inline_instances.append(inline)
return inline_instances
class DishTypeInline(ObjInline):
model = DishType
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
field = super(DishTypeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
if db_field.name == 'dishtype':
if self.obj is not None:
field.queryset = field.queryset.filter(restaurant__exact = self.obj)
else:
field.queryset = field.queryset.none()
return field
class RestaurantAdmin(ObjAdmin):
inlines = [
DishTypeInline
]

Categories