I have models:
class Emp(models.Model):
full_name = models.CharField(max_length=100)
mobile = models.CharField(max_length=10,blank=True,null=True)
email = models.EmailField(blank=True,null=True)
class Enquiry(models.Model):
date = models.DateField(default=timezone.now)
number = models.AutoField(primary_key=True)
products = models.ManyToManyField(Product, related_name="products")
referred_by_emp = models.ForeignKey(
Emp,related_name='ref_emp',
null=True,blank=True,
)
The serializer classes:
class ReferrerSerializer(serializers.ModelSerializer):
class Meta:
model = Emp
fields = (
'full_name','mobile','email',
)
class EnquirySerializer(serializers.ModelSerializer):
class Meta:
model = Enquiry
fields = (
'name','mobile','email',
'products',
'referred_by_emp',
)
I wish to get the attributes of Emp i.e full_name, mobile, email in the form while entering the Enquiry details. The views for the 2 models:
class RefViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Emp instances to be viewed or edited.
"""
model = Emp
queryset = Emp.objects.all()
serializer_class = ReferrerSerializer
class EnquiryViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Enquiry instances to be viewed or edited.
"""
model = Enquiry
queryset = Enquiry.objects.all()
serializer_class = EnquirySerializer
While entering the Enquiry details in the django REST api, I wish to capture the Emp details also and submit the form. And the details should be captured in the respective models. How can this be achieved? I am new to django's REST Api and didn't find a proper way of doing it. Plz guide me with detailed code to achieve this. I tried itertools.chain, but perhaps didn't use it correctly Further I would like to invoke the curl command for the same.Thanx in advance
Using django 1.6.5
Ok this is not something i recommend doing, but I've have been there when it just has to happen.
class ReferrerSerializer(serializers.ModelSerializer):
class Meta:
model = Emp
fields = ('full_name','mobile','email')
class EnquirySerializer(serializers.ModelSerializer):
class Meta:
model = Enquiry
fields = ('name','mobile','email','products',)
# As the fields doesn't already exist you can 'copy' them into the serializer
EnquirySerializer.base_fields["full_name"] = ReferrerSerializer().fields["full_name"]
# Then in the view we can create another model instance.
# you should use the Serializer or a Form class and not save the data directly to a Emp instance here but i left that out for clarity.
class SomeViewSet(ModelViewSet):
model = Enquiry
# post() should only handle new/create calls, but if you want it more clear you can override create() instead.
def post(self, request, *args, **kwargs):
self.emp_data = {}
self.emp_data["full_name"] = self.request.DATA.pop("full_name")
return super(SomeViewSet, self).post(request, *args, **kwargs)
def pre_save(self, obj):
emp = Emp.objects.create(**self.emp_data)
obj.referred_by_emp = emp
return obj
Related
I'm trying to return the name of the pricing field but all I get is its foreign key id instead. What am I doing wrong here? I looked at some similiar issues on here but I didn't find anything that resembled my situation.
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = (
"assignedteams",
"agent",
"facility",
"organisor",
"avatar",
)
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
class UserSerializer(UserDetailsSerializer):
profile = UserProfileSerializer(source="userprofile")
subscription = UserSubscriptionSerializer(source="usersubscription")
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('profile', 'subscription',)
def update(self, instance, validated_data):
userprofile_serializer = self.fields['profile']
userprofile_instance = instance.userprofile
userprofile_data = validated_data.pop('userprofile', {})
usersubscription_serializer = self.fields['subscription']
usersubscription_instance = instance.usersubscription
usersubscription_data = validated_data.pop('usersubscription', {})
# update the userprofile fields
userprofile_serializer.update(userprofile_instance, userprofile_data)
usersubscription_serializer.update(usersubscription_instance, usersubscription_data)
instance = super().update(instance, validated_data)
return instance
You have 2 options to solve this problem.
option1:
If you want to return only the name of your pricing model you can use SlugRelatedField to do it.
Example:
class UserSubscriptionSerializer(serializers.ModelSerializer):
pricing = serializers.SlugRelatedField('name', readonly=True)
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
Option2:
If you want to return the Pricing object you can create a new ModelSerializer for your Pricing model and use it.
Example:
class PricingSerializer(serializers.ModelSerializer):
class Meta:
model = Pricing
fields = ["id","name"]
class UserSubscriptionSerializer(serializers.ModelSerializer):
pricing = PricingSerializer(readonly=True)
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
There are some other options that can you use but you must explain more about your problem can I will help you with.
you can easily add a new field representation or override the pricing field when want to represent data
so in your serializer add the following code
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
def to_representation(self, instance):
data = super().to_representation(instance)
data['pricing_name'] = instance.pricing.name # or replace the name with your pricing name field
return data
As you are saying pricing returned FK id, so i assume pricing column inside Subscription model is a FK to another model, let's assume it Pricing model.
You can create a serializer for Pricing and use it on UserSubscriptionSerializer,
like the way you created UserProfileSerializer and UserSubscriptionSerializer for UserSerializer
But, using directly a nested serializer will give you problem while doing write operation since as far i can understand you are accepting pricing as FK value when creating or updating
To solve this issue you can do some if/else on get_fields() method
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
def get_fields(self):
fields = super().get_fields()
# make sure request is passed through context
if self.context['request'] and self.context['request'].method == 'GET':
fields['pricing']=PricingSerializer()
return fields
Now coming back to the question, since you only need the pricing name which i assume name is a column on Pricing model
simply rewrite the previous code as
def get_fields(self):
fields = super().get_fields()
# make sure request is passed through context
if self.context['request'] and self.context['request'].method == 'GET':
fields['pricing'] = serializers.CharField(source='pricing.name', read_only=True)
return fields
P.S: I haven't tested this code on my computer
My application has the following structure:
models.py
class EventHost(models.Model):
hostid = models.ForeignKey(Host, on_delete=models.PROTECT)
eventid = models.ForeignKey(Event, on_delete=models.CASCADE)
someparam = models.CharField(max_length=100, blank=True, null=True)
...
class Meta:
unique_together = ("hostid", "event")
class Host(models.Model):
hostid = models.IntegerField(primary_key=True)
hostname = models.CharField(max_length=100)
...
class Event(models.Model):
eventid = models.AutoField(primary_key=True)
eventname = models.CharField(max_length=100)
...
hosts = models.ManyToManyField(Host, through='EventHost')
views.py
class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.order_by('-date')
serializer_class = EventSerializer
class HostViewSet(viewsets.ModelViewSet):
queryset = Host.objects.order_by('-hostid')
serializer_class = HostSerializer
class EventHostViewSet(viewsets.ModelViewSet):
queryset = EventHost.objects.all()
serializer_class = EventHostSerializer
Currently to update EventHost table I'm doing http put providing id (which is primary key) in my url .
I'd like to be able to update EventHost providing hostname and eventname (which will be passed in url) instead of id.
Using SQL it would look like this:
update eventhost set someparam='somevalue' from eventhost as a, event as b, host as c where b.id = a.eventid and c.hostid = a.hostid and b.eventname='myevent' and c.hostname='myhost';
From documentation I understood that I would need to modify the default update method for the viewset or/and modify queryset. Any ideas how should it be achieved?
You can override get_object method and use your parameters provided from url like that:
class EventHostViewSet(viewsets.ModelViewSet):
queryset = EventHost.objects.all()
serializer_class = EventHostSerializer
def get_object(self):
return EventHost.objects.get(hostid=self.kwargs['hostid'],eventid=self.kwargs['eventid'])
In this way, you must manage if there is no record with this query scenario, as custom
Assuming that you have properly constructed URL.
Edited EventHostViewSet.get_object method:
class EventHostViewSet(viewsets.ModelViewSet):
...
lookup_field = 'eventname'
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
filter_kwargs = {'hostid__hostname': self.kwargs.get('hostname'),
'eventid__eventname': self.kwargs.get('eventname')}
obj = get_object_or_404(queryset, **filter_kwargs)
self.check_object_permissions(self.request, obj)
return obj
EventHostViewSet registration:
router.register(rf'event-hosts/(?P<hostname>[.]+)', EventViewSet)
Some comments about your problem:
You will have problem when in your system will exist two EventHost with same hostid__hostname and eventid__event because queryset get method should only return ONE record
in DRF PUT method needs all fields to be provided in request data, if you want to update selected fields you should use PATCH method or override update viewset method (set partial to True)
EDITED AGAIN:
This is really bad design, you should not do this like that (somehow you should use id / maybe #action decorator to construct specific url for updating like that).
Here is my view:
class SectorListAPI(generics.ListAPIView):
queryset = SectorModel.objects.all()
serializer_class = SectorSerializer
Here is my serializers:
class OrganizationSerializer(serializers.ModelSerializer):
class Meta:
model = GroupProfile
fields = ('title','slug',)
class DepartmentSerializer(serializers.ModelSerializer):
organizations = OrganizationSerializer(many=True, read_only=True)
class Meta:
model = DepartmentModel
fields = ('title', 'organizations',)
class SectorSerializer(serializers.ModelSerializer):
# title = serializers.CharField()
departments = DepartmentSerializer(many=True, read_only=True)
class Meta:
model = SectorModel
fields = ('title','departments',)
Look, here 'SectorSerializer' is parent 'DepartmentSerializer' is children and 'OrganizationSerializer' is grand children serializer. Now in my view I can easily filter my queryset for 'SectorModel'. But how can i filter on 'GroupProfile' model.
You might want to filter the queryset to ensure that only results relevant to the currently authenticated user making the request are returned.
You can do so by filtering based on the value of request.user.
For example:
from myapp.models import Purchase
from myapp.serializers import PurchaseSerializer
from rest_framework import generics
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
This view should return a list of all the purchases
for the currently authenticated user.
"""
user = self.request.user
return Purchase.objects.filter(purchaser=user)
EDIT
You can subclass the ListSerializer and overwrite the to_representation method.
By default the to_representation method calls data.all() on the nested queryset. So you effectively need to make data = data.filter(**your_filters) before the method is called. Then you need to add your subclassed ListSerializer as the list_serializer_class on the meta of the nested serializer.
1- subclass ListSerializer, overwriting to_representation and then calling super
2- Add subclassed ListSerializer as the meta list_serializer_class on the nested Serializer.
Code relevant to yours:
class FilteredListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(user=self.request.user, edition__hide=False)
return super(FilteredListSerializer, self).to_representation(data)
class OrganizationSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FilteredListSerializer
model = GroupProfile
fields = ('title','slug',)
class DepartmentSerializer(serializers.ModelSerializer):
organizations = OrganizationSerializer(many=True, read_only=True)
class Meta:
model = DepartmentModel
fields = ('title', 'organizations',)
class SectorSerializer(serializers.ModelSerializer):
# title = serializers.CharField()
departments = DepartmentSerializer(many=True, read_only=True)
class Meta:
model = SectorModel
fields = ('title','departments',)
Thanks to #ans2human for the inspiration behind this answer.
Here's a new approach that is working great for me. I have several Models with is_active = BooleanField(...) that I need to filter out in nested relationships.
NOTE: this solution does not filter out results on non-list fields. for that, you should look to the primary queryset on your View
The core of the work is done by overloading the to_representation() function on a custom ListSerializer, and the many_init on an accompanying custom ModelSerializer:
class FilteredListSerializer(serializers.ListSerializer):
filter_params:dict
def __init__(self, *args, filter_params:dict={"is_active":True}, **kwargs):
super().__init__(*args, **kwargs)
self.filter_params = filter_params
def set_filter(self, **kwargs):
self.filter_params = kwargs
def to_representation(self, data):
data = data.filter(**self.filter_params)
return super().to_representation(data)
class FilteredModelSerializer(serializers.ModelSerializer):
LIST_SERIALIZER_KWARGS = serializers.LIST_SERIALIZER_KWARGS + ("filter_params",)
LIST_ONLY_KWARGS = ('allow_empty', 'filter_params')
#classmethod
def many_init(cls, *args, **kwargs):
list_kwargs = dict()
for arg in cls.LIST_ONLY_KWARGS:
value = kwargs.pop(arg, None)
if value is not None:
list_kwargs[arg] = value
child_serializer = cls(*args, **kwargs, **({"read_only":True} if "read_only" not in kwargs else dict()))
list_kwargs['child'] = child_serializer
list_kwargs.update({
key: value for key, value in kwargs.items()
if key in cls.LIST_SERIALIZER_KWARGS
})
meta = getattr(cls, 'Meta', None)
list_serializer_class = getattr(meta, 'list_serializer_class', FilteredListSerializer)
return list_serializer_class(*args, **list_kwargs)
Then, your custom ModelSerializer for whatever view would instead just extend FilteredModelSerializer instead.
class ChildSerializer(FilteredModelSerializer):
is_active = BooleanField() # not strictly necessary, just for visibilty
... # the rest of your serializer
class ParentSerializer(serializers.ModelSerializer):
children = ChildSerializer(many=True)
...# the rest of your parent serializer
Now, the children field on the ParentSerializer will filter for is_active = True.
If you have a custom query that you wanted to apply, you can do so by providing a dict of filter params in the standard queryset format:
class ParentSerializer(serializers.ModelSerializer):
children = ChildSerializer(many=True, filter_params={"my_field":my_value, "my_datetime__gte": timezone.now()})
...# the rest of your parent serializer
Alternatively, one could also utilize the set_filter(...) method on the FilteredListSerializer after instantiating the field, like so. This will yield a more familiar format to the original QuerySet.filter(...) style:
class ParentSerializer(serializers.ModelSerializer):
children = ChildSerializer(many=True)
children.set_filter(my_field=my_value, my_datetime__gte=timezone.now())
...# the rest of your parent serializer
I have a model which contains sensitive data, let's say a social security number, I would like to transform that data on serialization to display only the last four digits.
I have the full social security number stored: 123-45-6789.
I want my serializer output to contain: ***-**-6789
My model:
class Employee (models.Model):
name = models.CharField(max_length=64,null=True,blank=True)
ssn = models.CharField(max_length=16,null=True,blank=True)
My serializer:
class EmployeeSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField()
class Meta:
model = Employee
fields = ('id','ssn')
read_only_fields = ['id']
You can use SerializerMethodField:
class EmployeeSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField()
ssn = SerializerMethodField()
class Meta:
model = Employee
fields = ('id','ssn')
read_only_fields = ['id']
def get_ssn(self, obj):
return '***-**-{}'.format(obj.ssn.split('-')[-1]
If you don't need to update the ssn, just shadow the field with a SerializerMethodField and define get_ssn(self, obj) on the serializer.
Otherwise, the most straightforward way is probably to just override .to_representation():
def to_representation(self, obj):
data = super(EmployeeSerializer, self).to_representation(obj)
data['ssn'] = self.mask_ssn(data['ssn'])
return data
Please add special case handling ('ssn' in data) as necessary.
Elaborating on #dhke’s answer, if you want to be able to reuse this logic to modify serialization across multiple serializers, you can write your own field and use that as a field in your serializer, such as:
from rest_framework import serializers
from rest_framework.fields import CharField
from utils import mask_ssn
class SsnField(CharField):
def to_representation(self, obj):
val = super().to_representation(obj)
return mask_ssn(val) if val else val
class EmployeeSerializer(serializers.ModelSerializer):
ssn = SsnField()
class Meta:
model = Employee
fields = ('id', 'ssn')
read_only_fields = ['id']
You can also extend other fields like rest_framework.fields.ImageField to customize how image URLs are serialized (which can be nice if you’re using an image CDN on top of your images that lets you apply transformations to the images).
i have a very simple django model:
class Car(models.Model):
carname = models.CharField(max_length=100)
carmodel = models.CharField(max_length=100)
carcountry = models.CharField(max_length=100)
caryear = models.CharField(max_length=100)
cardesc = models.TextField()
and a admin.py that shows the all records:
class UserForm(forms.ModelForm):
cardesc = forms.CharField(widget=forms.Textarea(attrs={'cols': 120, 'rows': 20}))
class Meta:
model = Cars
class ModelAdmin(admin.ModelAdmin ):
form = UserForm
admin.site.register(Cars,ModelAdmin)
it works fine. now, my question is that:
i want to have a drop down list and i can select car model and then my results filtered based my choice...
how i can do that? i have to edit Admin.py file? Django has a builtin feature for this? or i have to create an HTML template? how? please help me.
Try this .
class CustomForm(forms.ModelForm):
class Meta:
model = Cars
cars_list = Cars.objects.values_list('carmodel', flat=True).order_by('carmodel').distinct()
choice_list = []
for car in cars_list:
choice_list.append((car,car,))
CAR_CHOICES = choice_list
#Used 'ChoiceField' as you want dropdown list for carmodels stored in text fields
carmodel = forms.ChoiceField(widget=forms.Select, choices=CAR_CHOICES)
class CarsModelAdmin(model.ModelAdmin):
form = CustomForm
list_filter = ['carmodel']
admin.site.register(Cars,CarsModelAdmin)
You would have to add
list_filter = ['carmodel', ]
to class ModelAdmin.
From the documentation:
ModelAdmin.formfield_for_foreignkey(db_field, request, **kwargs)¶
The formfield_for_foreignkey method on a ModelAdmin allows you to override the default formfield for a foreign keys field. For example, to return a subset of objects for this foreign key field based on the user:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This uses the HttpRequest instance to filter the Car foreign key field to only display the cars owned by the User instance.