I have several viewsets, several endpoints in them use one serializer. One endpoint does not even have a Meta class,
It performs a certain action and uses the same serializer in the method to_representation. In this serializer I use the methodfield like this:
some_field = serializers.SerializerMethodField()
def get_some_field(self, obj):
return bool(obj.something_attr)
something_attr I get in viewset in
queryset =MyModel.objects.annotate(something_attr=(...))
In others viewsets there is no such field, so they use other queriesets. Can I work around this problem without creating a bunch of additional serializers. My thanks!
As per my understanding you're trying to use a single serializer class with different views - but each view need different fields, right?
class DynamicFieldSerializerMixin:
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields', None)
super().__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
You can use this mixin to define fields dynamically for each view for a single serializer class. Just pass fields=[] keyword argument with list of field names.
Related
I want to have a FilterSet class that allows all of the fields to be filtered as a list.
I was thinking of overriding the default FilterSet class of django-filters and having the fields be dynamically set as well as setting a method to each one.
So, something like this for example:
ModelName_fields = {'names': 'name', 'ids': 'id', 'second_ids': 'second_id'}
class ListFilter(FilterSet):
def __init__(self, *args, **kwargs):
# self._meta.fields = [field_name for field_name in modelNameHere + '_fields']
super(FilterSet).__init__(*args)
class SomeAPIView(mixins.ListModelMixin):
model = ModelName
filterset_class = ListFilter
Basically, ModelName_fields is a declared constant that maps the query parameter to the field name of the model. In this case, I declare the model on the view as well as the filterset class and in the __init__ method of the filterset class, I dynamically attach the fields as well as the query parameter name.
In all essence, I just want to make the ListFilter as generic as possible to be used on different views as well.
My question is, is this the correct way or is there some other better way to accomplish this? Also, how can I get the name of the model, which is an attribute of the view class, in the ListFilter class?
I have a DRF 3.3+ API to create/update/retrieve users. I use the super-convenient write_only serializer field argument on my password field so that it's used to create/update a user, but is not returned when serializing a user. However, I want to make password required to create a user, but optional to update a user. Instead of write_only, it would be great to have something like create_only and update_only for finer-grained control. Since that's not available, I have two serializers that are exactly the same except for the password field, which doesn't seem clean.
I'm aware of this answer for DRF 2: Disable field update after object is created, but I was hoping there's a better way to handle this use case in DRF 3.3+. Thanks in advance for any insight.
There's no create_only or update_only like options.
You could override __init__ to see if the instance parameter was passed and adjust fields accordingly.
I'd have two serialisers, just as you do. So as not to repeat too much, I have one subclass the other, with the subclass adjusting only the fields that differed.
I think there is no inbuilt functionality for that.
But you could do something like this to define the fields depening on the action:
class FooSerializer(serializers.ModelSerializer):
# define fields depending on action
_action_fields = {'update': ['name'],
'create': ['name', 'password'],
'default': ['name', 'password']}
class Meta:
model = Foo
fields = ['name', 'password'] # define max fields you want serialize
def __init__(self, *args, **kwargs):
serializers.ModelSerializer.__init__(self, *args, **kwargs)
action = kwargs['context']['view'].get('action', 'default') # I'm not 100% sure if action is defined here. But something similar
allowed = set(self._action_fields[action])
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
I am having an issue with serialization. I have a queryset of objects e.g.:
uvs = UserVehicles.objects.all()
Some of those objects are expired, some are not. I would like to have different fields in serializer, depending on expiry information. For example, I would like to exclude status and distance_travelled fields from expired objects. What is the easiest way to achieve that? I tried with the next code, but self.object in init method contains an array, so it would remove fields for all objects, not just expired ones.
serialized_data = UserVehicleSerializer(uvs, many=True).data
class UserVehicleSerializer(serializers.ModelSerializer):
class Meta:
model = UserVehicle
fields = ('type', 'model', 'status', 'distance_travelled',)
def __init__(self, *args, **kwargs):
super(UserVehicleSerializer, self).__init__(*args, **kwargs)
if self.object.is_expired:
restricted = set(('distance_travelled', 'status',))
for field_name in restricted:
self.fields.pop(field_name)
I would suggest keeping the business logic out of the serializer. you could create a separate serializer for expired vehicles/objects and a separate serializer for active vehicles and choose the correct serializer in the view. That way, if your business logic goes in different directions for each type of vehicle , it should be easy to manage.
You could do that in the Serializer's to_representation().
http://www.django-rest-framework.org/api-guide/fields/#custom-fields has examples for Fields but Serializers do inherit from Field.
Just call the parent's to_representation and remove the fields you don't want.
I have an order model with a followed_by field:
class order(models.Model):
followed_by = models.ForeignKey(User, limit_choices_to={'groups__name': "Managers"})
I have several such models and forms for those models. By default the form displays a modelchoicefield listing users that are mangers. This is fine. But the display isn't nice: it gives the username, and I want first+last name. This would work nicely: Change Django ModelChoiceField to show users' full names rather than usernames
except that now in everyform I must declare the queryset to limit users to managers. Can I use the above method so that the custom modelchoicefield defaults to my filtered queryset. so then from a form I can just say:
followed_by = ManagerUserModelChoiceField()
Can you define the queryset on your ModelChoiceField child class?
class UserModelChoiceField(ModelChoiceField):
# Query that returns set of valid choices
queryset = User.objects.filter(group__name='Managers')
def label_from_instance(self, obj):
return obj.get_full_name()
Try passing in the queryset as an argument to the ManagerUserModelChoiceField class.
followed_by = ModelChoiceField(queryset = User.objects.filter(groups__name="Managers")
After my comment to #Enrico this thought occurred to me: I overwrote the "init" class on my custom field like so:
class UserModelChoiceField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
super(UserModelChoiceField, self).__init__(queryset=User.objects.filter(groups__name="Managers"), *args, **kwargs)
I've seen stuff like this done in python before but I'm new to python so I'm not sure if this is a bad thing to do or if I should make this better somehow? I'd appreciate some feedback. That being said, it seems to be working correctly.
Suppose I want to create and update a model. What fields are displayed and the type of validation depends on the action (create or update). But they still share a lot of the same validation and functality. Is there a clean way to have a ModelForm handle this (besides just if instance exists everywhere) or should I just create two different model forms?
Two possibilities spring to mind. You could set an attribute in the form's __init__ method, either based on a parameter you explicitly pass in, or based on whether self.instance exists and has a non-None pk:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
# either:
self.edit = kwargs.pop('edit', False)
# or:
self.edit = hasattr(self, instance) and self.instance.pk is not None
super(MyModelForm, self).__init__(*args, **kwargs)
# now modify self.fields dependent on the value of self.edit
The other option is to subclass your modelform - keep the joint functionality in the base class, then the specific create or update functionality in the subclasses.