Dynamic Fields On A Serializer For A ForeignKey - django

I set up a serializer that is able to dynamically serialize the desired fields as specified in the Django Rest Framework docs.
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__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)
>>> class UserSerializer(DynamicFieldsModelSerializer):
>>> class Meta:
>>> model = User
>>> fields = ['id', 'username', 'email']
>>>
>>> print(UserSerializer(user))
{'id': 2, 'username': 'jonwatts', 'email': 'jon#example.com'}
>>>
>>> print(UserSerializer(user, fields=('id', 'email')))
{'id': 2, 'email': 'jon#example.com'}
How would I then add in a model that is related by ForeignKey to this and dynamically serialize the desired fields from that model?
We could make the models
class User(models.Model):
id = IntegerField()
username = CharField()
email = EmailField()
class Vehicle(models.Model):
color = CharField()
type = CharField
year = DateField()
driver = ForeignKey(User)
and then based on the view either include color, type, year, or any combination of those.
I would like something like this.
{
'id': 27,
'username': 'testuser',
'vehicle: {
'color': 'Blue',
'type': 'Truck',
}
}

If you want a nested dict you could simply do
class VehicleSeralizer(serialzers.ModelSerializer):
class Meta:
model = Vehicle
fields = '__all__'
class UserSerializer(seralizers.ModelSerializer):
vehicle = VehicleSerializer()
class Meta:
model = User
fields = ['id', 'username', 'email'] # you might need to add 'vehicle'

Related

Django: Add extra attributes to form fields generated by UpdateView

Am using a custom user that is a subclass of Django AbstractUser, what am trying to archive is to allow user update their data everything works but the form look ugly. Below is my code the class attribute is not added to the form.
forms.py(simplified)
class AccountEditForm(forms.ModelForm):
class Meta:
model = CustomUser
fields = ('first_name', 'last_name', 'phone_number', 'date_of_birth', 'country')
widget = {
'first_name':forms.TextInput(
attrs={
'class': 'input-bordered',
}
)
}
views.py
class UserAccountDetails(LoginRequiredMixin, UpdateView):
template_name = 'dashboard/account_edit.html'
context_object_name = 'form'
form_class = AccountEditForm
model = CustomUser
def get_object(self, queryset=None):
"""
Return the object the view is displaying.
"""
if queryset is None:
queryset = self.get_queryset()
#Get logged in user from request data
queryset = queryset.filter(pk=self.request.user.id)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
The widgets option is for overriding the defaults on explicitly declared fields. To add class to the field you have many options.
Option #1: Explicitly declare form field and add class through widgets in Meta.
class AccountEditForm(forms.ModelForm):
first_name = forms.TextField(widget=forms.TextInput())
class Meta:
model = CustomUser
fields = ('first_name', 'last_name', 'phone_number', 'date_of_birth', 'country')
widgets = {
'first_name': forms.TextInput(
attrs={
'class': 'input-bordered',
}
)
}
Option #2: Shorter version of option #1.
class AccountEditForm(forms.ModelForm):
first_name = forms.TextField(widget=forms.TextInput(attrs={'class': 'input-bordered'}))
class Meta:
model = CustomUser
...
Option #3: Add class in form's __init__ method.
class AccountEditForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(AccountEditForm, self).__init__(*args, **kwargs)
self.fields['first_name'].widget.attrs['class'] = 'input-bordered'
Option #4: Use django-widget-tweaks plugin.

how to add search_fields in django forms

I was making a django forms and there is a field owner which is related with ForeignKey by User model , Sometimes name of user is same so I want to search it by their email address , How can I add searching of email field in forms like this search_fields = ['email'].
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = ('name', 'owner', 'club', 'moderator', 'group_type', 'country')
def __init__ (self, *args, **kwargs):
# brand = kwargs.pop("brand")
super(GroupForm, self).__init__(*args, **kwargs)
language_results = User.objects.all()
# self.fields["owner"].widget = forms.widgets.CheckboxSelectMultiple()
# self.fields["owner"].widget = autocomplete.ModelSelect2()
self.fields["owner"] = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=True,
widget = forms.SelectMultiple(attrs={
'placeholder': "Choose the users(s)",
'class': 'chzn-select',
'multiple tabindex': '6',
}))
you just use a CharField and add that form in your template;and after submiting that search you get the value entred by the user and use the objects filter to return the corresponding result.

Returning the human-readable element of a Choice Field in DRF serializer

How do I return the human readable element of a Choice field in a Serializer Class. Sample code below.
from rest_framework import serializers
from model_utils import Choices
from django.utils.translation import ugettext_lazy as _
COMPANY_TYPE = Choices(
(1, 'Public', _('Public Company')),
(2, 'Private', _('Private Company')),
(3, 'Other', _('Other Type')),
)
class CompanySerializer(serializers.ModelSerializer):
company_type = serializers.ChoiceField(choices=COMPANY_TYPE)
company_type_name = serializers.ReadOnlyField(source=COMPANY_TYPE[1]) # <=== This is the issue
class Meta:
model = Company
fields = ('id', 'title', 'company_type', 'company_type_name')
If say an entry in the company table has company_type = 1, and a user makes an API request, I want to include the extra field of company_type_name with the value Public Company.
So the issue is am unable to pass the current value of company_type to the serializer so that it can return the String value of the Choice Field.
You can do it with method field and by get_Foo_dispay()
company_type_name = serializers.SerializerMethodField()
def get_company_type_name(self, obj):
return obj.get_company_type_display()
From the DRF Oficial DC the choices must be a list of valid values, or a list of (key, display_name) tuples
So your choices must be in following format,
COMPANY_TYPE = (
(1, 'Public'),
(2, 'Private'),
(3, 'Other'),
)
NB : model_utils.Choices does the same thing
I think you need a SerializerMethodField with read_only=True rather than a ReadOnlyField. So Change your serializer as below,
class CompanySerializer(serializers.ModelSerializer):
def get_company_type_name(self, obj):
return COMPANY_TYPE.__dict__.get('_display_map').get(obj['company_type'])
company_type = serializers.ChoiceField(choices=COMPANY_TYPE)
company_type_name = serializers.SerializerMethodField(read_only=True, source='get_company_type_name')
class Meta:
model = Company
fields = ('id', 'title', 'company_type', 'company_type_name')

Overwrite maxlength/minlength of username by Django User model in the ModelForm

Try to overwrite User models by the following code, but somehow I cannot overwrite the max_length and min_length of username.
More specifically, when I check by python manage.py shell, I do overwrite them. But it seems has no effect on the html which was rendered(username maxlength is still 150).
Don't know which parts get wrong, please help.
from django import forms
from django.contrib.auth.models import User
class RegistrationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
email = self.fields['email']
username = self.fields['username']
email.required = True
email.label_suffix = ' '
username.label_suffix = ' '
######### this is not work!!!###############
username.min_length = 6
username.max_length = 30
############################################
class Meta:
model = User
fields = ('username', 'email')
labels = {
'username': '帳號',
}
help_texts = {
'username': '',
}
Instead of modifying the form, you should modify/override the model.
I recommend using django-auth-tools for building your own custom user model. It supplies basic models, views and forms which can be easily extended.
If you are trying to override just the model form field, you could do something like this
class RegistrationForm(forms.ModelForm):
username = forms.CharField(required=True, min_length=6, max_length=30)
class Meta:
model = User
fields = ('username', 'email')
or
class RegistrationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
self.fields['username'] = forms.CharField(required=True, min_length=6, max_length=30)
class Meta:
model = User
fields = ('username', 'email')
But I would recommend creating a Custom User Model inherited from AbstractBaseUser to override the username or email field as documented in https://docs.djangoproject.com/en/1.10/topics/auth/customizing/

DRF: Manipulating serializer field layout

I have a model that represents a house:
class House(models.Model):
name = models.CharField(...)
long = models.FloatField(...)
lat = models.FloatField(...)
and a serializer to return a list of houses in their most basic representation:
class HouseSerializer(serializers.ModelSerializer):
class Meta:
model = House
fields = ('id', 'name')
and the view
class HouseList(generics.ListAPIView):
queryset = House.objects.all()
serializer_class = HouseSerializer
this works fine. I can visit /api/house/ and I see a json list of houses:
{
'id': 1,
'name': 'Big House'
},
{
'id': 1
'name': 'Small House',
}...
Now I want to create a second view/resource at /api/maps/markers/ that returns my houses as a list of Google-Map-Friendly markers of the format:
{
'id': 1,
'long': ...,
'lat': ...,
'houseInfo': {
'title': "Big House",
}
} ...
I can foresee two approaches:
perform this as a separate serializer (using the same view as before) and mapping out the alternative field layout.
perform this as a separate view (using the same serializer as before) and simply layout the fields before creating a Response
but in neither approach am I clear on how to go about it nor which approach is preferable?
Answer 1
Looks to me like you need both - different view and serializer.
Simply because the view endpoint is not a sub-url of the first one, so they are not related - different view, even if they use the same model.
And different serializer - since you have a different field layout.
Not really sure how complicated is your case, but any code duplication can probably be solved by mixins anyway.
Answer 2
Depending on the use case:
if you also need to write data using the same struct, you need to define your own field class and handle the parsing correctly
if it's just reading data, you should be fine with this:
class HouseGoogleSerializer(HouseSerializer):
houseInfo = serializers.SerializerMethodField('get_house_info')
class Meta:
model = House
fields = [...]
def get_house_info(self, obj):
return {'title': obj.name}
where HouseSerializer is your base house serializer.
this code come from a running project and offer somethig more that you ask
but can easily adapted for your need if you want remove some features.
The current implemetation allow you:
use only one url one serializer and one view
choose the output using query string param (?serializer=std)
how to use in your code:
Case 1 (one url with ability to choose the serializer via querystring)
class HouseSerializer(HouseSerializer):
houseInfo = serializers.SerializerMethodField('get_house_info')
class Meta:
model = House
def get_house_info(self, obj):
return {'title': obj.name}
class HouseList(DynamicSerializerMixin, generics.ListAPIView):
queryset = House.objects.all()
serializer_class = HouseSerializer
serializers_fieldsets = {'std': ('id', 'name'),
'google' : ('id', 'long', 'lat', 'houseInfo')}
Case 2 (different views)
class HouseList(DynamicSerializerMixin, generics.ListAPIView):
queryset = House.objects.all()
serializer_class = HouseSerializer
serializers_fieldsets = {'std': ('id', 'name')}
class GoogleHouseList(DynamicSerializerMixin, generics.ListAPIView):
queryset = House.objects.all()
serializer_class = HouseSerializer
serializers_fieldsets = {'std': ('id', 'long', 'lat', 'houseInfo')}
==============
def serializer_factory(model, base=BaseHyperlinkedModelSerializer,
fields=None, exclude=None):
attrs = {'model': model}
if fields is not None:
attrs['fields'] = fields
if exclude is not None:
attrs['exclude'] = exclude
parent = (object,)
if hasattr(base, 'Meta'):
parent = (base.Meta, object)
Meta = type(str('Meta'), parent, attrs)
if model:
class_name = model.__name__ + 'Serializer'
else:
class_name = 'Serializer'
return type(base)(class_name, (base,), {'Meta': Meta, })
class DynamicSerializerMixin(object):
"""
Mixin that allow to limit the fields returned
by the serializer.
Es.
class User(models.Model):
country = models.ForeignKey(country)
username = models.CharField(max_length=100)
email = models.EmailField()
class UserSerializer(BaseHyperlinkedModelSerializer):
country = serializers.Field(source='country.name')
class MyViewSet(DynamicSerializerViewSetMixin, BaseModelViewSet):
model = User
serializer_class = UserSerializer
serializers_fieldsets = {'std': None,
'brief' : ('username', 'email')
}
this allow calls like
/api/v1/user/?serializer=brief
"""
serializers_fieldsets = {'std': None}
serializer_class = ModelSerializer
def get_serializer_class(self):
ser = self.request.QUERY_PARAMS.get('serializer', 'std')
fields = self.serializers_fieldsets.get(ser, 'std')
return serializer_factory(self.model,
self.serializer_class,
fields=fields)