Django Serializer defining initial object - django

I can't seem to figure out how to pass in an initial value to a serializer. I have a multitenant django site and I am trying to now get APIs setup. The client field exists but needs to be hidden and read only. I thought this worked like a form and a view in traditional django. I would normally pass in the get_initial in the view. I tried that first but it doesn't work. I think I need to get the value directly in the serializer but I can't seem to get it to work.
model:
class Location(ClientAwareModel):
name = models.CharField(max_length=64, blank=True)
address = models.CharField(max_length=64)
address2 = models.CharField(max_length=64, blank=True)
city = models.CharField(max_length=64)
state = USStateField()
zip_code = USZipCodeField()
class Client(models.Model):
name = models.CharField(max_length=100)
subdomain_prefix = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.name
class ClientAwareModel(models.Model):
client = models.ForeignKey(Client, on_delete=models.PROTECT)
class Meta:
abstract = True
def hostname_from_request(request):
# split on `:` to remove port
return request.get_host().split(':')[0].lower()
def client_from_request(request):
hostname = hostname_from_request(request)
subdomain_prefix = hostname.split('.')[0]
return Client.objects.filter(subdomain_prefix=subdomain_prefix).first()
serializer (you can see all my failed attempts commented out:
class LocationSerializer(serializers.ModelSerializer):
def get_client(self, obj):
# return client_from_request(self.request)
return client_from_request(self.context['request'])
# client = serializers.SerializerMethodField('get_client')
# client = serializers.SerializerMethodField()
# client = serializers.Field(source='get_client', read_only=True)
# client = serializers.ReadOnlyField(source='get_client')
# client = serializers.PrimaryKeyRelatedField(read_only=True, default='get_client')
client = serializers.PrimaryKeyRelatedField(read_only=True, source='get_client')
# client = serializers.HiddenField(default=get_client(self))
class Meta:
model = Location
fields = ['name', 'address', 'address2', 'city', 'state', 'zip_code', 'client']
viewset:
class LocationViewSet(viewsets.ModelViewSet):
queryset = Location.objects.all()
serializer_class = LocationSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
client = client_from_request(self.request)
return super().get_queryset().filter(client=client)
you can see the different ways I tried to pass in the value but no matter what I do I get
IntegrityError at /locations/
null value in column "client_id" violates not-null constraint

One easy way to pass the client object to serializer is to pass it in perform_create method, something like:
from rest_framework import serializers
class LocationViewSet(viewsets.ModelViewSet):
queryset = Location.objects.all()
serializer_class = LocationSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
client = client_from_request(self.request)
return super().get_queryset().filter(client=client)
def perform_create(self, serializer):
client = client_from_request(self.request)
if not client:
raise serializers.ValidationError("Client does not exist")
serializer.save(client=client)
And also remove the client field from your serializer:
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = ['name', 'address', 'address2', 'city', 'state', 'zip_code']

In your viewset you are trying to get Location by filtering Client when your Location model does not have a Client FK.
Right here:
def get_queryset(self):
client = client_from_request(self.request)
return super().get_queryset().filter(client=client) <----
As #gregory mentioned in the comment above, adding Foreign Key would solve your problem, then you can just simply add it to your serializer.

Related

DRF Invalid pk object does not exist for foreign key on POST create

I have two classes related to each other.
One class I have made the primary key a char field so I can easily reference to it or create it to match the id of the actual object (all objects will have this unique name).
from django.db import models
class Ven(models.Model):
id = models.CharField(max_length=80, primary_key=True)
statusOn = models.BooleanField(default=True)
class Device(models.Model):
device_id = models.CharField(max_length=80)
ven_id = models.ForeignKey(VEN, related_name='devices', on_delete=models.SET_NULL, null=True)
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ['id', 'device_id', 'ven_id']
class VENSerializer(serializers.ModelSerializer):
class Meta:
model = VEN
fields = ['id', 'statusOn', 'devices']
class DeviceList(generics.ListCreateAPIView):
logger.info("DeviceList: view")
# permission_classes = [permissions.IsAuthenticated]
queryset = Device.objects.all()
serializer_class = DeviceSerializer
however when I try to run my test:
class DevicesTestCase(TestCase):
def setUp(self):
self.factory = Client()
def test_create_device(self):
device = {
"ven_id": "TEST_VEN_1",
"device_id": "TEST_DEVICE_1",
"statusOn": True,
}
response = self.factory.post('/devices/', data=device)
self.assertEqual(response.status_code, 201)
my response returns 400 and states:
b'{"ven_id":["Invalid pk \\"TEST_VEN_1\\" - object does not exist."]}'
I'm trying to write a custom create in my serializer to create the ven if it does not exist but it's not being called. Data is being validated else where. My breakpoint in my view set's perform_create also does not fire.
I don't want to write a bunch of workaround code for something that should be straightforward. I know I'm missing/messing something up somewhere.
I think you need to customize the create method in the DeviceSerializer.
class DeviceSerializer(serializers.ModelSerializer):
ven_id = serializers.CharField()
status_on = serializers.BooleanField(write_only = True)
class Meta:
model = Device
fields = ['id', 'device_id', 'ven_id']
def create(self, validated_data):
ven_id = validated_data.pop('ven_id')
status_on = validated_data.pop('status_on')
try:
ven = Ven.objects.get(id = ven_id)
except Ven.DoesNotExist:
ven = Ven.objects.create(id = ven_id, statusOn = status_on)
new_device = Device.objects.create(ven_id = ven, **validated_data)
return new_device

Django API issue trying to create a method to add lookup data from another table

Django API Issue
I am wanting to append a data field [site_name] stored in the parent table [USERaffiliates] to a child record [USERaffylinks].
site_name method: I am doing this so that I have access to the site_name field on a Vue.js component ... where I display the link data -- –
There is a 1:Many relationship with 1 USERaffiliates having many USERaffylinks.
I would so love some help - I have tried so many things but keep getting errors in the API when I try adding a new USERaffylinks record via the API browser screen.
I tried adding this method > def site_name - but failed.
Here's the traceback
traceback image
Without the site_name method, the API works just fine.
def site_name(self): is wrong somehow.
class USERaffylinks(models.Model):
owner_link_useraffyid = models.ForeignKey(USERaffiliates, on_delete=models.CASCADE)
owner_link_short = models.CharField(max_length=27, null=True, default=None, blank=True)
def site_name(self):
if USERaffiliates.objects.filter(id=self.owner_link_useraffyid).exists():
sitename = USERaffiliates.objects.get(id=self.owner_link_useraffyid)
return sitename.site_name
else:
return "ERROR"
FULL MODELS { without the def site_name(self) } -- API works fine with just this
class USERaffiliates(models.Model):
owneruserid = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
user_category = models.BigIntegerField(null=True, default=None, blank=True )
registered_email = models.EmailField(null=True)
site_name = models.CharField(max_length=40, null=True)
... ... ....
class USERaffylinks(models.Model):
owner_link_useraffyid = models.ForeignKey(USERaffiliates, on_delete=models.CASCADE)
owner_link_short = models.CharField(max_length=27, null=True, default=None, blank=True)
... ... ...
USERaffylinks View
#action(methods=['POST'], detail=True)
class AffyPartnerLinksViewSet(viewsets.ModelViewSet):
queryset= USERaffylinks.objects.all() #need to filter on login user > owneruserid=request.user.id
serializer_class = USERaffylinksSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,) #later IsAuthentiated
Serialiser File
class USERaffiliatesSerializer(serializers.ModelSerializer):
class Meta:
model = USERaffiliates
fields = '__all__'
extra_fields = ['user_category_desc','user_status_desc','num_affy_links']
extra_kwargs = {'owneruserid': {'required':True}}
def get_field_names(self, declared_fields, info):
expanded_fields = super(USERaffiliatesSerializer, self).get_field_names(declared_fields, info)
if getattr(self.Meta, 'extra_fields', None):
return expanded_fields + self.Meta.extra_fields
else:
return expanded_fields
class USERaffylinksSerializer(serializers.ModelSerializer):
class Meta:
model = USERaffylinks
fields = '__all__'
extra_fields = ['site_name']
extra_kwargs = {'owner_link_useraffyid': {'required':True}}
def get_field_names(self, declared_fields, info):
expanded_fields = super(USERaffylinksSerializer, self).get_field_names(declared_fields, info)
if getattr(self.Meta, 'extra_fields', None):
return expanded_fields + self.Meta.extra_fields
else:
return expanded_fields
You are facing this error due to the wrong use of the actions decorator. As rest_framework's viewsets docs suggest that you can use actions decorator for making some extra actions for routing. In your case you are trying actions decorator on class which mean, you are making your whole class a routable which only accept POST method.
so instead of using this:
#action(methods=['POST'], detail=True)
class AffyPartnerLinksViewSet(viewsets.ModelViewSet):
queryset= USERaffylinks.objects.all() #need to filter on login user > owneruserid=request.user.id
serializer_class = USERaffylinksSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,) #later IsAuthentiated
try this:
class AffyPartnerLinksViewSet(viewsets.ModelViewSet):
queryset= USERaffylinks.objects.all() #need to filter on login user > owneruserid=request.user.id
serializer_class = USERaffylinksSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,) #later IsAuthentiated
Hope this will solve your issue.
USERaffylinks.owner_link_useraffyid is a ForeignKey so when accessed it will perform a DB query to access the related object. You need to fix the way you check to see if the ForeignKey contains a valid id, simply access the field and catch any DoesNotExist errors
class USERaffylinks(models.Model):
owner_link_useraffyid = models.ForeignKey(USERaffiliates, on_delete=models.CASCADE)
owner_link_short = models.CharField(max_length=27, null=True, default=None, blank=True)
def site_name(self):
try:
return self.owner_link_useraffyid.site_name
except USERaffiliates.DoesNotExist:
return "ERROR"
If the ForeignKey can be null (or at least invalid) then maybe it would make sense to make the field nullable instead of having a default value

Django Rest Framework - How to set the current_user when POST

I have just followed a small tutorial using DRF, but I can't figure how to like my model to his user when POSTing a new object
this is my model :
# Create your models here.
class Project(models.Model):
# project title
title = models.CharField(max_length=255, null=False)
# subtitle
subtitle = models.CharField(max_length=255, null=False)
#######
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
and so my serializer
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ("id", "title", "subtitle", "user_id")
so, now in the view I have access to the current_user with this :
request.user
class ListProjectsView(generics.ListCreateAPIView):
authentication_classes = [authentication.TokenAuthentication]
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def list(self, request):
queryset = Project.objects.filter(user_id=request.user.id)
serializer = ProjectSerializer(queryset, many=True)
return Response(serializer.data)
[...]
def create(self, request, pk = None):
return super(ListProjectsView, self).create(request, pk = None)
I suppose there is a way to passe the request.user is the create in order to allow my Project.user_id to be filled ?
Whatever I'm doing, I can't manage to set the user, and i get the
django.db.utils.IntegrityError: null value in column "user_id" violates not-null constraint error
Any idea?
Thanks!
Try to override with following method. Everytime PUT/PATCH/CREATE operation is performed following method is called. This is the good way to pass the current user.
def perform_create(self, serializer):
serializer.save(user = self.request.user)
class Project(models.Model):
# project title
title = models.CharField(max_length=255, null=False)
# subtitle
subtitle = models.CharField(max_length=255, null=False)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
okay so you have FK user but try to access it with the user_id = request.user.id
Just call
queryset = Project.objects.filter(user=request.user)
queryset = Project.objects.filter (user_id=request.user.id)
if you want to match the id you should put two __
like so user__id = request.user.id but I dont see any sence making it.

Django REST Framework - queryset filter only for authenticated user

I'm new to Django REST Framework. I think I have gone messed up in it as sometimes it feels like DRF is easy to understand but later it got messed up.
I have a contacts application.
contacts/models.py
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
class ContactEmail(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
email = models.EmailField()
class ContactPhoneNumber(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
phone = models.CharField(max_length=100)
Each contact is related some authenticated user.
contacts/serializers.py
class ContactPhoneNumberSerializer(serializers.ModelSerializer):
class Meta:
model = ContactPhoneNumber
fields = ('id', 'phone')
class ContactEmailSerializer(serializers.ModelSerializer):
class Meta:
model = ContactEmail
fields = ('id', 'email')
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
emails = ContactEmailSerializer(source='contactemail_set', many=True)
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'date_of_birth',
'phone_numbers', 'emails')
and contacts/views.py
class ContactViewSet(viewsets.ModelViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
as far till here, I followed the doc of Django REST Framework but it displays all contacts instead of showing only user's contacts.
To achieve that, I added get_queryset()
class ContactViewSet(viewsets.ModelViewSet):
# queryset = Contact.objects.all()
...
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
...
But it started giving error as
assert queryset is not None, '`base_name` argument not specified, and could ' \
AssertionError: `base_name` argument not specified, and could not automatically determine the name from the viewset, as it does not have a `.queryset` attribute.
after doing some research, I found to add third parameter to router
app/urls.py
router = routers.DefaultRouter()
router.register(r'contacts', ContactViewSet, 'contacts') # added 'contacts' here
urlpatterns = [
path('api/', include(router.urls))
]
But instead of solving the issue, it generated a new error
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked
relationship using view name "contact-detail".
You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
again after doing some research, found solution to define url in searializers.py. So, I updated my contacts/serializers.py
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ...
emails = ...
url = serializers.HyperlinkedRelatedField(
view_name='contacts:detail'
)
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'date_of_birth',
'phone_numbers', 'emails')
But now, it has started giving a new error
'Relational field must provide a `queryset` argument, '
AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.
Is'nt there some simple way just to filter objects based on request.user as my application is user based and every authenticated user can view/edit/delete his own data only.
Also, is there any way to get this issue resolved as I'm confused here which Relational field it is talking about?
this solved my issue
set read_only=True to url
url = serializers.HyperlinkedRelatedField(
view_name='contacts:detail',
read_only=True
)
I havent worked with ModelViewSet.
But in a regular APIView, you can do this:
class SingleProject(APIView):
def get(self, request):
project_id = request.query_params.get('id')
ans = Project.objects.filter(id=project_id)
serializer = EagerGetProjectSerializer(qs, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
so maybe something like this:
class ContactViewSet(viewsets.ModelViewSet):
queryset = Contact.objects.filter(user=self.request.user)
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)

django rest framework : nested model get not working. 'str' object has no attribute 'values'

I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
Saving is working perfectly. But now it is showing the following error when I call the GET method.
AttributeError at /api/v1/customer 'str' object has no attribute 'values'
Request Method: GET
bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
bcusomer/serializers.py
class CustomerDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = BCustomer
fields = ('city', 'phone')
class CustomerSerializer(serializers.ModelSerializer):
customer_details = CustomerDetailsSerializer()
class Meta:
model = get_user_model()
fields = ('id','first_name', 'email', 'customer_details')
def create(self, validated_data):
request = self.context.get('request')
customer_details_data = validated_data.pop('customer_details')
customer_user = get_user_model().objects.create(**validated_data)
BCustomer.objects.create(customer=customer_user, user=request.user, **customer_details_data)
customer_user.customer_details = customer_details_data
return customer_user
class CustomerListSerializer(serializers.ModelSerializer):
model = get_user_model()
fields = '__all__'
class Meta:
model = get_user_model()
fields = '__all__'
bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
def get_queryset(self):
queryset = BCustomer.objects.all()
return queryset
def get_serializer_class(self):
if self.action == 'list' or self.action == 'retrieve':
return CustomerListSerializer
return CustomerSerializer
bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
Data post parameter format
{
"first_name":"Myname",
"email":"testemail#gmail.com",
"customer_details": {
"city":"citys",
"phone":"04722874567",
}
}
You should remove model and fields from CustomListSerializer
class CustomerListSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = '__all__'
customer_details = CustomerDetailsSerializer()
You need to set the source argument to point to the user model's customer. Most probably:
customer_details = CustomerDetailsSerializer(source='customer')
(or maybe source='bcustomer', not sure if it reversed the field name or class name).
On a side not, you should not need the ListSerializer at all. The list method will call the serializer with the many=True argument on CustomerSerializer which will create the ListSerializer appropriately.