I'm trying to access the dictonary inside the jsonfield serializer "assigned_facilities". But i'm receiving the following error:
django.db.utils.IntegrityError: null value in column "assigned_facilities_id" of relation "users_leadfacilityassign" violates not-null constraint
DETAIL: Failing row contains (78, null, null, 159).
File "/app/users/api/views.py", line 53, in perform_create
serializer.save(agent=self.request.user)
File "/usr/local/lib/python3.9/site-packages/rest_framework/serializers.py", line 205, in save
self.instance = self.create(validated_data)
File "/app/users/api/serializers.py", line 252, in create
instance.leadfacility.create(assigned_facilities_id=assigned_facilities.get('facility_id'), datetime=assigned_facilities.get('datetime'))
I'm basically trying to create a "LeadFacilityAssign" object for each item inside my json so i can have a "LeadFacilityAssign" object for each facility i want to add to a lead.
Does anyone know what is causing this error? I tried a few different things but nothing worked so far.
json
{
"facilities": [{
"facility_id": "1",
"datetime": "2018-12-19 09:26:03.478039"
},
{
"facility_id": "1",
"datetime": "2018-12-19 09:26:03.478039"
}
]
}
serializers.py
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"is_owner",
"assigned_facilities",
)
read_only_fields = ("id", "is_owner")
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
def create(self, validated_data):
assigned_facilities = validated_data.pop("assigned_facilities")
instance = Lead.objects.create(**validated_data)
for facility in assigned_facilities:
instance.leadfacility.create(assigned_facilities_id=assigned_facilities.get('facility_id'), datetime=assigned_facilities.get("datetime"))
return instance
models.py
class Facility(models.Model):
name = models.CharField(max_length=150, null=True, blank=False)
def __str__(self):
return self.name
class Lead(models.Model):
first_name = models.CharField(max_length=40, null=True, blank=True)
last_name = models.CharField(max_length=40, null=True, blank=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class LeadFacilityAssign(models.Model):
assigned_facilities = models.ForeignKey(Facility, on_delete=models.CASCADE, related_name='leadfacility')
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='leadfacility')
datetime = models.DateTimeField()
views.py
class LeadCreateView(CreateAPIView):
permission_classes = [IsAuthenticated, IsLeadOwner]
serializer_class = LeadUpdateSerializer
def perform_create(self, serializer):
serializer.save(agent=self.request.user)
class LeadUpdateView(UpdateAPIView):
permission_classes = [IsAuthenticated, IsLeadOwner]
serializer_class = LeadUpdateSerializer
def get_queryset(self):
return Lead.objects.all()
You have three tables:
Lead table in which lead_id is non nullable since it is primary key
Facility table in which facility_id is non nullable since it is primary key
LeadFacility table in which lead_facility_id is non nullable but its two foreign keys (lead_id and facility_id) are nullable.
And you are assigning these nullable value to your non nullable field.
Maybe you are trying to do :
lead_falility_id (table: LeadFacility talbe) = facility_id (table: Facility)
But by mistake you are doing :
lead_falility_id (table: LeadFacility talbe) = facility_id (table: LeadFacility).
And because of this, your are doing :
lead_facility_id = null for the non nullable field.
Your JSON doesn't match your serializer.
Your serializer fields don't match your model fields.
Your views don't match your models or your serializers.
So, let's take it from the top.
If I understand correctly, you want to create a LeadFacilityAssign object at the same time as creating or updating a Lead object. There are some approaches to solve this, like using a post_save signal right after a Lead create request, but let's follow your drift...
From your Lead serializer, this is "fine":
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"is_owner",
"assigned_facilities",
)
read_only_fields = ("id", "is_owner")
But this:
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
Has a comparison statement (==) in the last line, meaning it could return a True or False value (?), plus you're not using the "agent" field anywhere else, nor is declared in the serializer or even a field in your models. Just get rid of that function or add the "agent" field in your LeadFacilityAssign model (assuming you'll assign a Lead, a Facility and an Agent to that relationship).
What I'm guessing you're expecting from you JSON call is the "facilities" info. From your declared fields above I guess you should be expecting an "assigned_facilities" field, which doesn't show in your JSON data, but let's assume your API will receive an "assigned_facilities" field instead of the "facilities" sub-dict with many facilities related to a single Lead.
I haven't tested the code below, but according to the REST API docs, you have to define two methods in your serializer now, one for CREATE and one for UPDATE.
Create:
def create(self, validated_data):
lead = Lead.objects.create(first_name=validated_data['first_name'], last_name=validated_data['last_name'] #Here you will create the Lead object that you will reference later in your LeadFacilityAssign relationship with the dictionary information from the received data, so let's save it:
lead.save()
#Now we need to create all facilities relationships to this Lead:
facilities = validated_data['assigned_facilities'] #This will create a "facilities" sub-dict from your received data with a facility_id and a datetime field in key-value pair.
for item in facilities:
facility = Facility.objects.get(id=item['facility_id']) #Get a single facility object for each ID in your JSON. If this fails, try converting it to int().
datetime = item['datetime'] #Again, if it fails because it's taken as string, try converting it to datetime object.
entry = LeadFacilityAssign.objects.create(assigned_facilities=facility, lead=lead, datetime=datetime) #Create the entry.
entry.save() #Save the entry
return #Exit your function
The Update method should look more or less the same.
In the view, if you're not using the "agent" field just parse the user it for safety or just use it later if you want to include it as owner in your model.
class LeadCreateView(CreateAPIView):
permission_classes = [IsAuthenticated, IsLeadOwner]
serializer_class = LeadUpdateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Your JSON sample include "facilities" but your serializer has assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True).
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"PrimaryAddress",
"City",
"PostalCode",
"RegionOrState",
"pc_email",
"Cell",
"secphone",
"client_cell",
"client_secphone",
"birthday",
"curr_client_address",
"curr_client_city",
"curr_client_zip",
"ideal_address",
"ideal_city",
"ideal_zip",
"ideal_state",
"budget",
"client_email",
"client_first_name",
"client_last_name",
"lead_status",
"created_at",
"agent",
"is_owner",
"relationship",
"marital_status",
"gender",
"pets",
"facilities",
)
read_only_fields = ("id", "created_at", "agent", "is_owner")
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
def create(self, validated_data):
facilities = validated_data.pop("facilities", None)
instance = Lead.objects.create(**validated_data)
for facilities in assigned_facilities:
LeadFacilityAssign.objects.create(assigned_facilities_id=assigned_facilities.get("facility_id"), datetime=assigned_facilities.get("datetime"), lead=instance)
return instance
Also you added required False for facilities, so validated.pop("facilities") might be caused error if there is no facilities in request. You should add another parameter into pop method. validated.pop("facilities", None)
Your Query will become like this
instance.leadfacility.create(assigned_facilities_id__id=assigned_facilities.get('facility_id'), datetime=assigned_facilities.get("datetime"))
NOTE-
because assigned_facilities_id return full object of foreign key & assigned_facilities_id__id return value of id foreign key object
Related
I have a custom User model and a Group model that are linked by a UserGroup through model (Many to Many relationship):
models.py
class User(models.Model):
username = models.CharField(primary_key=True, max_length=32, unique=True)
user_email = models.EmailField(max_length=32, unique=False) # Validates an email through predefined regex which checks ‘#’ and a ‘.’
user_password = models.CharField(max_length=32)
user_avatar_path = models.CharField(max_length=64)
class Group(models.Model):
group_id = models.AutoField(primary_key=True)
group_name = models.CharField(max_length=32, unique=False)
group_admin = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='my_groups'
)
members = models.ManyToManyField(
User,
related_name='groups', # The name to use for the relation from the related object back to this one.
through='UserGroup' # Attaches a Junction table to the Many to Many relationship.
)
class UserGroup(models.Model): # Manually specified Junction table for User and Group
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='user_groups'
)
group = models.ForeignKey(
Group,
on_delete=models.CASCADE,
related_name='user_groups'
)
I'm trying to associate multiple users with a group, using a PATCH request to update the members attribute of a group. Using the following GroupSerializer, I'm able to associate a user as a member of the group when the group is created, by overriding the create function of the serializer:
serializers.py
class GroupSerializer(serializers.ModelSerializer):
members = MemberSerializer(many=True, required=False)
group_admin = serializers.SlugRelatedField(slug_field='username', queryset=User.objects.all()) # A Group object is related to a User object by username
class Meta:
model = Group
fields = ['group_id', 'group_name', 'group_admin', 'members']
def create(self, validated_data): # Overriden so that when a group is created, the group admin is automatically declared as a member.
group = Group.objects.create(**validated_data)
group_admin_data = validated_data.pop('group_admin')
group.members.add(group_admin_data)
return group
def update(self, instance, validated_data):
members_data = validated_data.pop('members') # Comes from the request body, gets the members list
#print('output: ' + str(members_data[0].items()))
add_remove = self.context['add_remove'] # Comes from the View
if members_data is not None:
if add_remove == 'add':
for member in members_data:
instance.members.add(member['username'])
elif add_remove == 'remove':
for member in members_data:
instance.members.remove(member['username'])
return super().update(instance, validated_data)
I'm not able to update the members associated with a group when overriding the update function of the serializer. The serializer is called from the following GroupUpdate view:
views.py
class GroupUpdate(generics.UpdateAPIView):
serializer_class = GroupSerializer
def get_object(self):
queryset = Group.objects.all()
group_id = self.kwargs['group_id']
if group_id is not None:
queryset = queryset.filter(group_id=group_id).first()
return queryset
def get_serializer_context(self): # Passes the URL paramters to the GroupSerializer (serializer doesn't have kwargs).
context = super().get_serializer_context()
context['add_remove'] = self.kwargs['add_remove']
print(self.request.data)
return context
def perform_update(self, serializer):
serializer=GroupSerializer(data=self.request.data, partial=True)
serializer.is_valid(raise_exception=True)
return super().perform_update(serializer)
Within the perform_update function of GroupUpdate, I receive the following: TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use members.set() instead. but I am unsure as to why this error would be raised, considering I was able to associate a user with a group in the create function in pretty much the same way.
This is what a PATCH request would have as the JSON body:
{
"members": [
{
"username": "small_man"
}
]
}
The output of self.request.data is {'members': [{'username': 'small_man'}]}.
You should specify instance of updated object when you create serializer otherwise serializer's save method will call create not update:
def perform_update(self, serializer):
instance = self.get_object()
serializer=GroupSerializer(instance, data=self.request.data, partial=True)
serializer.is_valid(raise_exception=True)
return super().perform_update(serializer)
BTW looks like perform_update is redundant and you can remove it since serializer validation should work without additional modifications.
I have a ModelSerializer which I'm using to create new posts. It has a field book, of type PrimaryKeyRelatedField(queryset=Book.objects.all(), pk_field=serializers.CharField(max_length=255)).
When I post to this endpoint I get the error:
{
"book": ["Incorrect type. Expected pk value, received str."]
}
How can this be, as my Primary Key is a CharField.
The thing that really throws me off is, that I tried circumventing this with a SlugRelatedField, but when I do this, I get a really long and weird error:
DataError at /api/content/posts/
value "9780241470466" is out of range for type integer
LINE 1: ...020-05-22T20:14:17.615205+00:00'::timestamptz, 1, '978024147..., I don't understand it at all, as I am not setting an integer.
Serializer Code:
class PostCreationSerializer(serializers.ModelSerializer):
book = serializers.PrimaryKeyRelatedField(queryset=Book.objects.all(), pk_field=serializers.CharField(max_length=255))
class Meta:
model = Post
fields = ['content', 'book', 'page', 'date_posted', 'user', 'id']
read_only_fields = ['date_posted', 'user', 'id']
Model Code:
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
pages = models.PositiveSmallIntegerField(null=True, blank=True)
image = models.URLField(null=True, blank=True)
date_published = models.CharField(max_length=4, null=True, blank=True)
publisher = models.CharField(max_length=140, null=True, blank=True)
isbn13 = models.CharField(max_length=13, primary_key=True)
objects = AutomaticISBNDBManager
def __str__(self):
return self.title
View Code:
class PostListCreate(UseAuthenticatedUserMixin, generics.ListCreateAPIView):
serializer_class = PostSerializer
queryset = Post.objects.order_by('-date_posted')
def get_serializer_class(self):
if self.request.method == 'POST':
return PostCreationSerializer
else:
return PostSerializer
Edit: The POST I'm sending:
{
"book": "9780241470466",
"content": "test",
"page": "10"
}
Note: the user and date_posted are set automatically.
You can use SlugRelatedField instead of PrimaryKeyRelatedField like that:
book = serializers.SlugRelatedField(
slug_field='isbn13',
queryset=Book.objects.all()
)
From the docs:
SlugRelatedField may be used to represent the target of the relationship using a field on the target.
For an alternative method, you can set book reference to your post model in serializer validate method:
1-) Replace PrimaryKeyRelatedField with CharField
2-) Find book object in your validate method and assign it to validated data.
class PostCreationSerializer(serializers.ModelSerializer):
book = serializers.CharField()
class Meta:
model = Post
fields = ['content', 'book', 'page', 'date_posted', 'user', 'id']
read_only_fields = ['date_posted', 'user', 'id']
def validate(self, attrs):
try:
attrs['book'] = Book.objects.get(isbn13=attrs['book'])
return attrs
except Book.DoesNotExist:
raise serializers.ValidationError("Book not found")
So, I don't really solved it, but I found out how to revert the problem:
I'm using PostgreSQL as a database backend. These problems seem to be coming from me choosing to use a custom primary key, or because my custom primary key, is a CharField. Luckily, I had made a DB Backup before making these changes, as I wasn't sure if everything would go smoothly and I reverted the code to using id as the primary key and used the SlugRelatedField to get the book.
So, the solution would be: Postgres doesn't like CharField as primary keys?
These are simplified versions of my models (the user model is just an id and name)
class Convo(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='convo_owner')
users = models.ManyToManyField(User, through='Convo_user')
class Convo_user (models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
class Comments(models.Model):
name = models.CharField(max_length=255)
content = models.TextField(max_length=1024)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
This is my view
class ConvoViewSet(viewsets.ModelViewSet):
serializer_class = serializers.ConvoSerializer
def get_queryset(self):
return None
def list(self, request):
curr_user = request.user.id
# Collecting the list of conversations
conversations = models.Conversation.object.filter(ConvoUser__user_id=request.user.id)
#Getting list of conversation id's
conv_ids = list(conversations.values_list('id', flat=True).order_by('id'))
#Getting list of relevant comments
comments = models.Comments.objects.filter(conversation_id__in=conv_ids)
return Response(self.get_serializer(conversations, many=True).data)
And my current serializer
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
# access = AccessSerializer(many=True)
# model = models.Comments
# fields = ('id', 'name', 'content', 'convo_id')
class Meta:
model = models.Convo
fields = ('id', 'owner_id')
The current response I get is of the form
[
{
"id": 1,
"owner_id": 32
}, ...
]
But I would like to add a comments field that shows all the properties of comments into the response, so basically everything in the second queryset (called comments) and I'm not sure how to go about this at all. (I retrieve the comments in the way I do because I'm trying to minimize the calls to the database). Would I need to create a new view for comments, make its own serializer and then somehow combine them into the serializer for the convo?
The way you've set up your models, you can access the comments of each Convo through Django's ORM by using convo_object.comments_set.all(), so you could set up your ConvoSerializer to access that instance's comments, like this:
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
comments_set = CommentSerializer(many=True)
class Meta:
model = models.Convo
fields = ('id', 'owner_id', 'comments_set')
and then you define your CommentSerializer like:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Comments
fields = ('id', 'name', 'content')
No data appears because my serializers are using the default database, not sure why but a step forward
EDIT:
Django: Database used for prefetch_related is not the same that the parent query Provided me the correct answer, I was able to choose the database with this method because for some reason inner queries use the default DB
I have 2 models that are OneToOne related and model that is FK to 2nd model
models.py
class Legal(TimeStampedModel):
name = models.CharField('Name', max_length=255, blank=True)
class LegalCard(TimeStampedModel):
legal = models.OneToOneField('Legal', related_name='legal_card', on_delete=models.CASCADE)
branch = models.ForeignKey('Branch', related_name='branch', null=True)
post_address = models.CharField('Post address', max_length=255, blank=True)
class Branch(TimeStampedModel):
name = models.CharField('Name',max_length=511)
code = models.CharField('Code', max_length=6)
Using DRF I made them to behave as single model so I can create or update both:
serializer.py
class LegalSerializer(serializers.ModelSerializer):
branch = serializers.IntegerField(source='legal_card.branch', allow_null=True, required=False)
post_address = serializers.CharField(source='legal_card.post_address', allow_blank=True, required=False)
class Meta:
model = Legal
fields = ('id',
'name',
'branch',
'post_address',
)
depth = 2
def create(self, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
legal = super(LegalSerializer, self).create(validated_data)
self.update_or_create_legal_card(legal, legal_card_data)
return legal
def update(self, instance, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
self.update_or_create_legal_card(instance, legal_card_data)
return super(LegalSerializer, self).update(instance, validated_data)
def update_or_create_legal_card(self, legal, legal_card_data):
LegalCard.objects.update_or_create(legal=legal, defaults=legal_card_data)
views.py
class LegalDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Legal.objects.all()
serializer_class = LegalSerializer
I'm trying to save this by sending FK as integer (I just want to post id of the branch), but I receive error
ValueError: Cannot assign "2": "LegalCard.branch" must be a "Branch" instance.
Is there any way to pass over only ID of the branch?
Thank you
In Django, if you only need the FK value, you can use the FK value that is already on the object you've got rather than getting the related object.
Assume you have a Legal and Branch object with id's as 1. Then you can save a LegalCard object by:
LegalCard(legal_id=1,branch_id=1,post_address="Istanbul Street No:1")
Just use legal_card.branch_id instead of legal_card.branch to get just an id, not a related object.
And depth = 1
actually I don't have a form, I am currently using the ViewSet to create API.
I have two models, one is a regular user model and a group model which uses a field named creator with ForeignKey to the user
I created a serializer then use it to validate the POST inputs, somehow I keep on getting the error saying
null value in column "creator_id" violates not-null constraint
Which I can understand and I tested too that it's because creator_id is null when I try to post, but I did enter the creator_id though. Am I entering it in a wrong way or how do I get the foreign key in order to save it?
My group model is like this
class Group(Model):
creator = ForeignKey(User,
null=True, // I used this to test I am right with the error
blank=True, // I used this to test I am right with the error
on_delete=CASCADE,
related_name="%(class)ss",
related_query_name="%(class)s")
group_name = CharField(max_length=256, unique=True)
created_at = DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.group_name
again, I added null=True, blank=True is because I want to see if I am right that it's because of creator_id can't be null and yes I am right, after adding these two fields, I don't get the error anymore.
my GroupSerializer
class GroupSerializer(ModelSerializer):
group_id = SerializerMethodField()
creator_name = SerializerMethodField()
creator_id = SerializerMethodField()
class Meta:
model = Group
fields = ['group_id', 'group_name', 'creator_id', 'creator_name']
def get_group_id(self, obj):
return obj.id
def get_creator_name(self, obj):
return obj.creator.tutor_name
def get_creator_id(self, obj):
return obj.creator.id
def create(self, validated_data):
return Group.objects.create(**validated_data)
my view.py
#csrf_exempt
def create(self, request):
data = GroupSerializer(data=request.data) // this would return the `group_name` and `creator_id` I entered
if data.is_valid(): // this would return true
data.save()
return Response({'status': True})
return Response({'status': False, 'message': data.errors}, status=400)
I tried posting using...
{
"group_name": "bridge",
"creator_id": 1
}
tried "creator_id": "1" and "creator": "1"
still getting the same error...
How can I resolve this?
Thanks in advance
I think you are declaring your serializer wrong. You are using a ModelSerializer, but the fields you are declaring in the fields list don't match the model's fields. Also note that fields is a list, not an array. I think it should be something like this.
class Group(Model):
creator = ForeignKey(User,
on_delete=CASCADE,
related_name="%(class)ss",
related_query_name="%(class)s")
group_name = CharField(max_length=256, unique=True)
created_at = DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.group_name
class GroupSerializer(ModelSerializer):
class Meta:
model = Group
fields = ('creator', 'group_name', 'created_at')
Also I know you were doing it to test, but its considered bad practice to declare both null=True and blank=True on the same field. It should be one or the other.
See the docs here: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer