I have one model that has a ManyToMany Field (let's call it "Options") with another Model
When I create the ModelForm it displays all options.
Is there any way to exclude some option values or to show only some of them?
Here is an example:
models.py
class Options (model.Models):
name = ...
...
class Anything (model.Models):
...
options = ManyToManyField(Options)
values of "Options" in my DB:
["OK",
"OK_2",
"NOT_OK",
"OK_3,
"NOT_OK_2"]
Let's say that I need to show ONLY the "OK" values and hide or not to show the "NOT_OK" values.
Is there any way to do this with ModelForms?
You certainly can filter the queryset for a foreign key field or m2m on the related model by using a Form or more commonly a ModelForm.
The reason doing this at form level is useful is because that filtering could well be based on business logic which is not applicable in all cases and so allows more flexibility than defining it against the model for example.
While you can do this while defining the form fields it is best to do it once the form has been constructed and so it takes place at runtime and not compile time (I have just experienced a few interesting occasions where this has caused me some issues, however that was an earlier version of Django!)
The following ModelForm would do the job:
class AnythingForm(ModelForm):
options = forms.MultipleChoiceField()
def __init__(self, **kwargs):
super(AnythingForm, self).__init__(self, **kwargs)
self.fields['options'].queryset = Option.objects.filter({pass in your filters here...})
class Meta:
model = Anything
You can pass the limit_choices_to parameter to your ManyToMany field:
from django.db.models import Q
class Anything (models.Model):
options = models.ManyToManyField(Options,
limit_choices_to=Q(name__startswith='OK'))
In django 1.7 you can even pass a callable in case if list of choices should be changed dynamically.
Related
So I just started using Django Rest Framework and one of my serializers has a MultipleChoiceField in which the choices are simply all the instances of another model.
Here is the serializer in question:
class ObjectTypeSerializer(serializers.ModelSerializer):
def get_field_choices():
return sorted([
(p.id, p.name) for p in Parameter.objects.all()
])
object_fields = serializers.MultipleChoiceField(
choices=get_field_choices()
)
instance_fields = serializers.MultipleChoiceField(
choices=get_field_choices()
)
labels = serializers.SlugRelatedField(
queryset=Label.objects.all(),
many=True, allow_null=True, slug_field='name'
)
class Meta:
model = ObjectType
fields = ('id', 'name', 'object_fields',
'instance_fields', 'labels')
However, when I add a new Parameter object, the choices are not updated. In regular Django forms, I solved this simply using
forms.ChoiceField(choices=[(p.id, p.name) for p in Parameter.objects.all()])
and it would update the choices when a new parameter is added without restarting the server. How can I accomplish the same thing with Django Rest Framework serializers?
Any help is appreciated. Thanks!
When your choices are models, the most straightforward approach is to use some derivative of RelatedField. Given that you're using p.id, does PrimaryKeyRelatedField work for you? (Please update your question if it doesn't)
If the default behavior (using model's __unicode__ for the display value) is not what you desire, you can always subclass it and redefine the display_value method:
class CustomPKRelatedField(serializers.PrimaryKeyRelatedField):
"""A PrimaryKeyRelatedField derivative that uses named field for the display value."""
def __init__(self, **kwargs):
self.display_field = kwargs.pop("display_field", "name")
super(CustomPKRelatedField, self).__init__(**kwargs)
def display_value(self, instance):
# Use a specific field rather than model stringification
return getattr(instance, self.display_field)
...
class ObjectTypeSerializer(serializers.ModelSerializer):
...
object_fields = CustomPKRelatedField(queryset=Parameter.objects.all(), many=True)
instance_fields = CustomPKRelatedField(queryset=Parameter.objects.all(), many=True)
...
...
If all you need is so BrowsableAPIRenderer would render a nice-looking <select>, I believe that's all you need to do.
The ChoiceField and MultipleChoiceField are designed to work on a static dataset. They even preprocess things at __init__ to allow grouping. This is why new items don't appear there - those fields essentially "cache" results forever (until the server restart).
If, for some reason, you really need it to be ChoiceField-derivative, you can set up post_save and post_delete singal listeners and update fields' choices (and grouped_choices if you're not on a very bleeding edge version where a PR to allow choices to be set dynamically is already included) attributes. Check the ChoiceField source code for the details. That would be a dirty hack, though. ;)
So I have follwoing models:
class A(models.Model):
name = models.CharField()
age = models.SmallIntergerField()
class B(models.Model):
a = models.OneToOneField(A)
salary = model.IntergerField()
Now I want to create one rest end point for there two as they are one to one. So I want following as get
{
url: 'http://localhost/customs/1/',
name: 'abc',
age: 24,
salary: 10000
}
Similary, I want to create records and update as well. Please let me know how can I achieve this in django rest framework 3.
I just encountered the same problem, it would indeed be useful to make the response structure less tied to the underlying model structure. Here's my take :
Reading is easy
Serializer fields have a source parameter, which can take dotted names to traverse attributes.
class ABSerializer(serializers.ModelSerializer):
class Meta:
model = A
fields = ['name', 'age', 'salary']
salary = serializer.IntegerField(source='b.salary') # this is your related_name
Writing is ... not officially supported
Validated data will show a nested structure, and the standard create and update methods will choke trying to assign a data dict to a OneToOneField.
The good news is that you can work around it by overriding create and update methods. Here's an example with update :
class ABSerializer(serializers.ModelSerializer):
class Meta:
model = A
fields = ['name', 'age', 'salary']
related_fields = ['b']
salary = serializer.IntegerField(source='b.salary') # this is your related_name
def update(self, instance, validated_data):
# Handle related objects
for related_obj_name in self.Meta.related_fields:
# Validated data will show the nested structure
data = validated_data.pop(related_obj_name)
related_instance = getattr(instance, related_obj_name)
# Same as default update implementation
for attr_name, value in data.items():
setattr(related_instance, attr_name, value)
related_instance.save()
return super(ABSerializer,self).update(instance, validated_data)
Of course, this example is very simplistic, doesn't do any exception handling, and won't work with more deeply nested objects... but you get the idea.
Another option
You could also create a read-write flavor of SerializerMethodField, which would consider both a getter and a setter, however that would probably end up being far more verbose in the end.
Hope that helps !
I know this is an old post but I came across this and after some research and reading through the Django Rest Framework documentation
So a quick search I found that you could use the related_name parameter for reverse relationships as stated here:
reverse relationships are not automatically included by the
ModelSerializer and HyperlinkedModelSerializer classes. To include
a reverse relationship, you must explicitly add it to the fields list.
For example:
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['tracks', ...]
You'll normally want to ensure that you've set an appropriate
related_name argument on the relationship, that you can use as the
field name.
For example:
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks',
on_delete=models.CASCADE)
...
If you have not set a related name for the reverse relationship,
you'll need to use the automatically generated related name in the
fields argument.
For example:
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['track_set', ...]
Also, see the Django documentation on reverse
relationships
for more details.
I have a Django Model with ManyToManyField in it. I need to require user to select at least one M2M value in this field.
I tried to set blank=False to M2M field but it didn't help.
class Skill(models.Model):
name = models.CharField(max_length=200)
class PersonSkills(models.Model):
person = models.ForeignKey('Person')
skill = models.ForeignKey('Skill')
class Person(models.Model):
name = models.CharField(max_length=200)
skills = models.ManyToManyField('Skill', through='PersonSkills')
p = Person(name='Bob')
p.save()
# success, but I expect that this should throw ValidationError, because I didn't select at least one Skill for this person
I can solve this situation with custom Form definition or with override save() method for Person model.
Is it possible to prevent create Person without at least one Skill selected, with set ManyToManyField options? Or I need to create custom logic to handle this situation? Thanks.
I use Django 1.7 and Python 3.4
Update 1. How to create ModelForm to control M2M? Because in cleaned_data I have only fields that I pass for Person form, and haven't data that I pass as M2M fields. I try to create object in Admin Site and control that Skills selected. I enter Skill's via inline.
# admin.py
class PersonSkillsInline(admin.TabularInline):
model = Person.skills.through
extra = 2
class PersonAdmin(admin.ModelAdmin):
inlines = [PersonSkillsInline]
admin.site.register(Person, PersonAdmin)
On a database level... no, that's not possible. Any enforcement of this will have to come from your application logic.
The reason is that every m2m relation has a record with a foreign key to both sides of the m2m relation. SQL cannot enforce the existence of the referencing side of a relationship, only of the referenced side of a relationship.
Furthermore, you can't enforce it in your model either, because the Person has to be created and saved before you can assign any many-to-many relations.
Your only options are to enforce it in the form or the view.
In an InlineModelAdmin this can easily be done by specifying min_num (1.7+):
class PersonSkillsInline(admin.TabularInline):
model = Person.skills.through
min_num = 1
extra = 2
How can I use two models with OneToOne relation in one form using the CreateView in Django 1.5?
My models are these:
class Act(models.Model):
name = models.CharField()
class DetailAct(models.Model):
detail = models.CharField()
act = models.OneToOneField(Act)
My forms
class ActForm(forms.ModelForm):
name = forms.CharField(widget=forms.TextInput())
class Meta:
model = models.Act
class DetailActForm(forms.ModelForm):
detail = forms.CharField(widget=forms.TextInput())
class Meta:
model = models.DetailAct
Thank you
You can use two Form objects in one <form> tag without problems. Just make sure that you pass prefix="form-1" to one of the forms (or both - as long as the prefixes are different) in your view. See this answer for an example.
Nope, you can't use built-in class based views for this. Or, at least, not on the high-level you'd expect. You can make your own view class or mixin that will work with two forms, but AFAIK Django doesn't provide one.
I've defined a models.py with a "FirstClass" which contains a ForeignKey relathionship to "SecondClass". The relathionship can't be Null.
The SecondClass is very expansive (90.000 records), and when i display the FirstClass html form, it requires too many time generating the "select box" field.
Therefore, when I let user update the object (I use create_update.update_object generic view), i don't want to display and update the value of the foreignkey field, but i don't know how to do this...
Create a ModelForm and pass it into the view, according to the docs.
Since the foreign key should always exist upon creation, it's safe to ignore it in the update.
class MyModelForm(forms.ModelForm):
class Meta:
model = FirstClass
exclude = ('SecondClass',)
# urls.py
(r'^foo/(?P<object_id>\d+)/$','django.views.generic.create_update.update_object',
{'form_class': MyModelForm})