adding to a manytomany field in django - django

I am trying to save a many to many tags field when creating a post.
this is my post request to create a new post
{
"name":"testpost1",
"caption":"test caption n",
"tags":[{"name":"tag1"}]
}
Models
class Tags(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
name = models.CharField(max_length=50, unique=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="created_tags")
time_created = models.DateTimeField(auto_now_add=True)
followers = models.ManyToManyField(User, related_name="following_tags")
posts = models.ManyToManyField('Posts', related_name='tags', symmetrical=True)
class Posts(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
name = models.CharField(max_length=50)
caption = models.TextField(max_length=1000)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
The serializer for Post and tag
class TagsSerializerMini(serializers.ModelSerializer):
class Meta:
model = Tags
fields = ('name', )
class PostsSerializer(QueryFieldsMixin, serializers.ModelSerializer):
created_by = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
tags = TagsSerializerMini(many=True, required=False)
class Meta:
model = Posts
fields = ('id', 'name', 'caption', 'tags', 'num_reactions', 'num_comments', 'created_by', 'posted_time',
'updated_time', )
read_only_fields = ('id', 'posted_time', 'updated_time', )
def create(self, validated_data):
tags_data = validated_data.pop('tags', [])
post = Posts.objects.create(**validated_data)
for tag in tags_data:
t, _ = Tags.objects.get_or_create(name=tag["name"], created_by=self.context['request'].user)
t.posts.add(post)
return post
Now the issue is, when i am addding a post to tag t.posts.add(post) it is throwing an Error django.db.utils.IntegrityError: FOREIGN KEY constraint failed. I tried adding a post from the shell also, its giving the same error.

I got similar problem and I solved it by using an intermediary model (using through), here is how should look your code:
models.py:
class Tags(models.Model):
# ...
posts = models.ManyToManyField('Posts', related_name='tags', symmetrical=True, through='PostsTag')
class PostTag(models.Model):
tag = models.ForeignKey(Tags)
post = models.ForeignKey(Posts)
serializer:
# ...
def create(self, validated_data):
tags_data = validated_data.pop('tags', [])
post = Posts.objects.create(**validated_data)
for tag in tags_data:
t, _ = Tags.objects.get_or_create(name=tag["name"], created_by=self.context['request'].user)
PostTag.objects.create(tag=t, post=post) # <----- Create m2m relation
return post
Note: I didn't tested the code, but hope this helps!

Related

How do I pull in data from multiple models into a specific model serializer?

I have this model that represents a bookmark or favorite. It has multiple foreign keys to other models. In the api I would like to pull in the data from each of the models that is referenced in the particular bookmark.
The model:
class Bookmark(models.Model):
marktype = models.CharField(max_length=10)
post = models.OneToOneField(Post, on_delete=models.CASCADE, null=True, blank=True)
question = models.OneToOneField(Question, on_delete=models.CASCADE, null=True, blank=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at")
class Meta:
verbose_name = "bookmark"
verbose_name_plural = "bookmarks"
ordering = ["created_at"]
db_table = "bookmarks"
def __str__(self):
return "{}'s bookmark".format(self.owner.username)
I tried to use a SerializerMethodField but I get an error: 'NoneType' object has no attribute 'id'
Here is the serializer
class BookmarkSerializer(serializers.ModelSerializer):
post = serializers.SerializerMethodField()
question = serializers.SerializerMethodField()
class Meta:
model = Bookmark
fields = '__all__'
def get_post(self, obj):
obj = Post.objects.get(id=obj.post.id)
post = ShortPostSerializer(obj)
return post.data
def get_question(self, obj):
obj = Question.objects.get(id=obj.question.id)
question = ShortQuestionSerializer(obj)
return question.data
what am I doing wrong please?
You can update your serializer like the following (You can short it as you want or use your ShortQuestionSerializer as well instead of QuestionSerializer),
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
class BookmarkSerializer(serializers.ModelSerializer):
post = PostSerializer()
question = QuestionSerializer()
class Meta:
model = Bookmark
fields = '__all__'

How to get a relation from a relation in a serializer?

I have 3 models:
class Artist(Timestamps):
name = models.CharField('Name', max_length=255, blank=True)
...
class Festival(Timestamps):
name = models.CharField(max_length=255, blank=True)
...
class Event(Timestamps):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE, null=True)
festival = models.ForeignKey(Festival, on_delete=models.CASCADE, null=True)
Now I wan't all the id's from the festivals an artist is playing. I have a serializer like this:
class ArtistFestivalSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = ('id', 'name', 'event_set')
But this only gives me the id's of the event. Any ideas how to get trough the Event to the Festival?
Thanks in advance
EDIT - the view is:
class FestivalArtists(generics.ListAPIView):
serializer_class = ArtistFestivalSerializer
def get_queryset(self):
queryset = Artist.objects.prefetch_related('event_set').filter(event__isnull=False).distinct().order_by('name')
return queryset
I think you need to add the custom field for that.
class ArtistFestivalSerializer(serializers.ModelSerializer):
festival_ids = serializers.SerializerMethodField(read_only = True)
class Meta:
model = Artist
fields = ('id', 'name', 'event_set', 'festival_ids')
def get_festival_ids(self, obj):
return list(Event.objects.filter(artist = obj).values_list('festival_id').distinct())

retrieving the last instance of a model in another model's modelSerializer in django rest framework

I am creating rest APIs for a website in which users can purchase one of the provided subscriptions.
In this website there is a user-info API which returns the information about the logged in user which can be used to show their info on the website.
The problem is that, the mentioned API's serializer is a modelSerializer on the "User" model and the information that I want to return is the instance of "Subscription" model which the latest instance of "SubPurchase" model refers to.
These are my serializers, models and views.And I need to somehow return the user's current subscription's ID and name along with the user's information. If you have any further questions, ask me in the comments and I'll answer them.
# models.py
class User(AbstractBaseUser, PermissionsMixin):
userID = models.AutoField(primary_key=True)
username = models.CharField(max_length=100, unique=True, validators=[RegexValidator(regex="^(?=[a-z0-9._]{5,20}$)(?!.*[_.]{2})[^_.].*[^_.]$")])
email= models.EmailField(max_length=100, unique=True, validators=[EmailValidator()])
name = models.CharField(max_length=100)
isSuspended = models.BooleanField(default=False)
isAdmin = models.BooleanField(default=False)
emailActivation = models.BooleanField(default=False)
balance = models.IntegerField(default=0)
objects = UserManager()
USERNAME_FIELD = 'username'
class Subscription(models.Model):
subID = models.AutoField(primary_key=True)
nameOf = models.CharField(max_length=50)
price = models.PositiveIntegerField()
salePercentage = models.PositiveIntegerField(default=0)
saleExpiration = models.DateTimeField(default=datetime.datetime.now, blank=True)
def __str__(self):
return f"{self.nameOf}"
class SubPurchase(models.Model):
price = models.PositiveIntegerField()
dateOf = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
subscription = models.ForeignKey(Subscription, null=True, on_delete=models.SET_NULL)
def __str__(self):
return self.subscription
# serializers.py
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
# views.py
class UserInfoViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserInfoSerializer
def get_queryset(self):
uID = getattr(self.request.user,'userID')
return get_user_model().objects.filter(userID=uID)
def get_object(self):
uID = getattr(self.request.user,'userID')
return self.queryset.filter(userID=uID)
Again, I need to change the UserInfoSerializer in a way that would give me the user's current subscription's name, ID and expiration date which would be 30 days after the purchase date
If you are only interested in the returned data, you can override the function to_representation of your serializer and create a serializer for your related model. If I understood correctly, the current subscription of your user is the last one (if sorted by "dateOf"). So something like that could do the trick
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = ('nameOf', 'id', 'saleExpiration ')
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
def to_representation(self, instance):
data = super().to_representation(instance)
current_subs = instance.subpurchase_set.order_by('dateOf').last().subscription
data['current_subscription'] = SubscriptionSerializer(instance=current_subs).data
return data
you can use NestedSerializers to achieve what you are looking for
basically, nested serialization is a method in which you can return, create, put..., into a model from another model, it goes like this..
models.py
class User(AbstractBaseUser, PermissionsMixin):
....
#user model data
class SubPurchase(models.Model):
...
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields =["anyfield you wanna include"]
class SubPurchaseSerializer(serializers.ModelSerializer):
class Meta:
model = SubPurchase
fields =["anyfield you wanna include"]
class UserInfoSerializer(serializers.ModelSerializer):
subpurchace = SubPurchaseSerializer()
subscription= SubscriptionSerializer() #later included in the fields of this serializer
class Meta:
model = get_user_model()
fields = ('userID','subpurchace', 'subscription', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')

Cannot query "object": Must be "User" instance

I am creating sample-api which have posts and followers. Post should visible to followers only
My models.py
from django.contrib.auth.models import User
class Post(models.Model):
creator = models.ForeignKey(User, related_name='creator_post_set', null=True, on_delete=models.CASCADE)
title = models.CharField(max_length=25)
created_date = models.DateTimeField(auto_now_add=True)
content = models.TextField()
likes = models.BigIntegerField(null=True)
comments = models.BigIntegerField(null=True)
class Follow(models.Model):
follower = models.ForeignKey(User, related_name='following', null=True, on_delete=models.CASCADE)
followed_on = models.DateTimeField(auto_now_add=True)
following = models.ForeignKey(User, related_name='follower',null=True, on_delete=models.CASCADE)
My serializers.py for the models:
class UserSerializer(ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = User.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
)
return user
class Meta:
model = User
fields = ('password', 'username', 'first_name', 'last_name',)
class PostListSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['title', 'content', 'created_date',]
class FollowSerializer(serializers.ModelSerializer):
class Meta:
model = Follow
fields = '__all__'
My views.py:
class PostList(generics.ListCreateAPIView):
serializer_class = PostListSerializer
follow_model = FollowSerializer.Meta.model
post_model = PostSerializer.Meta.model
def get_queryset(self):
try:
followers = self.follow_model.objects.get(follower_id =
self.request.user.id)
queryset = self.post_model.objects.get(creator__in = followers)
except self.follow_model.DoesNotExist:
queryset = None
return queryset
When I call this view it returns the following error:
Cannot query "Follow object (1)": Must be "User" instance.
I need help Thanks in Advance.
As I can see, Post model's creator is FKed to User model. So you need to query using User model instance, not Follower model.
You can use the following code:
following = self.request.user.following.all().values_list('follower', flat=True) # because of related name
queryset = self.post_model.objects.filter(creator_id__in = list(following))
Here I have first retrieved the user ids using self.request.following.all() by reverse relationship. Then I have extracted the user ids using values_list. After that, I have used it in Post.objects.filter(...) method.

Django Restful: Nested Serializers

I'm adding a 'tests' field to my 'Sample' model, where 'tests' will be a list of 'TestRequest' objects. Currently, I'm getting this error:
Got AttributeError when attempting to get a value for field `tests` on serializer `SampleSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Sample` instance.
Original exception text was: 'Sample' object has no attribute 'tests'.
'tests' is not a field on my model. I'm just trying to add it to the serialized data. Currently, I can get a nested serializer to work for 'klass' but that is because it's defined in the model.
Models:
class Sample(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
comments = models.TextField(null=True, blank=True)
klass = models.ForeignKey('samples.Batch', null=True, blank=True,
related_name='samples', verbose_name='Batch')
product = models.ForeignKey('customers.Product', blank=False)
NOTRECEIVED = 'nr'
RECEIVED = 'rc'
DISCARDED = 'dc'
DEPLETED = 'dp'
SAMPLE_STATUS = (
(NOTRECEIVED, 'not received'),
(RECEIVED, 'received'),
(DISCARDED, 'discarded'),
(DEPLETED, 'depleted'),
)
status = models.CharField(
max_length=2, choices=SAMPLE_STATUS, default=NOTRECEIVED)
is_recycling = models.BooleanField(default=False)
is_submitted = models.BooleanField(default=False)
received_date = models.DateTimeField(
_('date received'), null=True, blank=True)
class TestRequest(models.Model):
client = models.ForeignKey('customers.Client')
company = models.ForeignKey('customers.Company')
sample = models.ForeignKey('samples.Sample')
procedure_version = models.ForeignKey('registery.ProcedureVersion')
replicates = models.PositiveIntegerField(editable=True, null=True, blank=True)
created_date = models.DateTimeField('Date created', auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
comments = models.TextField('Comments', blank=True)
Serializers:
class TestSerializer(serializers.ModelSerializer):
href = serializers.HyperlinkedIdentityField(lookup_field='pk', lookup_url_kwarg='pk', read_only=True, view_name='samples_api:test-detail')
class Meta:
model = TestRequest
fields = ('id', 'href',)
class SampleBatchSerializer(serializers.ModelSerializer):
href = serializers.HyperlinkedIdentityField(
lookup_field='pk', lookup_url_kwarg='batch_pk', read_only=True, view_name='samples_api:batch-detail')
class Meta:
model = Batch
fields = ('id', 'href',)
class SampleSerializer(serializers.ModelSerializer):
tests = TestSerializer(many=True)
klass = SampleBatchSerializer(many=False)
class Meta:
model = Sample
# list_serializer_class = FilteredListSerializer
fields = ('id', 'name', 'tests', 'klass',)
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
Viewsets:
class TestRequestViewSet(viewsets.ModelViewSet):
"""
Viewset for the TestRequest model
"""
serializer_class = TestRequestSerializer
def get_queryset(self):
client = get_object_or_404(Client, user=self.request.user)
return TestRequest.objects.filter(company=client.company)
def perform_create(self, serializer):
# Override default creatation to provide request based information.
client = get_object_or_404(Client, user=self.request.user)
company = client.company
serializer.save(client=client, company=company)
class SampleViewSet(viewsets.ModelViewSet):
"""
Viewset for the Sample model
"""
serializer_class = SampleSerializer
def get_queryset(self):
client = get_object_or_404(Client, user=self.request.user)
return Sample.objects.filter(klass__company=client.company)
I would rather not have to add the field to the model. A 'Sample' can have many 'TestRequest's but a 'TestRequest' can only have one 'Sample'.
How do I get my serializer to add the 'tests' field that isn't in the model?
in your SampleSerializer. You have specified 'tests' which is not in your Sample class in your model...
Use nested SerializerMethodField as below....
tests = serializers.SerializerMethodField()
def get_tests(self, obj):
var=TestRequestSerializer(obj.id)
return var.data