I'm writing app in witch I store data in separate models. Now I need to combine this data to use it.
The problem.
I have three models:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=50, blank=True)
...
class Contacts(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user")
contact_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="contact_user")
class UserPhoto(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
url = models.CharField(max_length=220)
How can I get the current user contacts with their names and pictures like this (serialized)
{
{
"contact_user":"1",
"first_name":"Mark ",
"url":first picture that corresponds to contact_user id
},
{
"contact_user":"2",
"first_name":"The Rock",
"url":first picture that corresponds to contact_user id
}
}
Now I'm quering the Contacts model to get all contacts_user id's that he has connection to.
class MatchesSerializer(serializers.ModelSerializer):
class Meta:
model = Contacts
fields = '__all__'
depth = 1
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactsSerializer
def get_queryset(self):
return Contacts.objects.filter(user__id=self.request.user.id)
The thing you need to is to serialize the Contacts queryset to include the related User and UserPhoto objects for each contact.
Try to create a custom serializer for the Contacts model so:
class ContactSerializer(serializers.ModelSerializer):
contact_user = serializers.SerializerMethodField()
def get_contact_user(self, obj):
user = obj.contact_user
photo = user.userphoto_set.first()
return {
"id": user.id,
"first_name": user.first_name,
"url": photo.url if photo else None
}
class Meta:
model = Contacts
fields = ("contact_user",)
Then, modify the ContactViewSet to use this newly created serializer so:
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
def get_queryset(self):
return Contacts.objects.filter(user__id=self.request.user.id)
Note: Generally, Django models don't require s to be added as suffix since it is by default added, so it is better to modify it as Contact from Contacts.
Related
I'm working on a small project using Django Rest Framework, I have two models ( contacts and category)
So a contact can be in a category, I have a foreign key between the models, I would like to know how can I get data category name instead of getting the id number.
This is my code :
class Category(models.Model):
cat_name = models.CharField(blank=False, max_length=255)
comment = models.CharField(blank=False, max_length=255)
private = models.BooleanField(default=False)
allowed = models.BooleanField(default=False)
def __str__(self):
return self.name
class Contact(models.Model):
category = models.ForeignKey(Category, on_delete=models.DO_NOTHING)
first_name = models.CharField(max_length=60)
last_name = models.CharField(max_length=60)
My serializer
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
Result I get :
"first_name": "John",
"last_name": "Doe",
"category": 1 ( i want to get the name of the category instead of the id )
This is one possible solution
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
def to_representation(self, obj):
return {
"first_name": obj.first_name,
"last_name": obj.last_name,
"category": obj.category.cat_name
}
Try this:
class ContactSerializer(serializers.ModelSerializer):
category_name = serializers.SerializerMethodField('get_category_name')
def get_category_name(self, obj):
if obj.category_id:
return obj.category.cat_name
return ""
class Meta:
model = Contact
fields = "__all__"
I got into same situation.I think there is no need to write another function if you can achieve this by one line of code and adding it to fields using source.You can also try this:
class ContactSerializer(serializers.ModelSerializer):
category = serializers.CharField(source="category.cat_name", read_only=True)
class Meta:
model = Contact
fields = ['first_name','last_name', 'category']
I'm trying to create create a nested serializer using the Django Rest framework. The relationship is Profile X User but when i use Profile.objects.create(user=profile, **user_data) i get ValueError: Cannot assign "<Profile: Profile object (7)>": "Profile.user" must be a "User" instance..
This should be some rookie misunderstanding of models relationship definitions or the serializer declaration itself but I can't find anything around the docs. If someone can point me a direction I'll be gracefull.
models.py
class User(models.Model):
name = models.CharField(max_length=100, blank=False)
email = models.CharField(max_length=100, blank=True, default='')
password = models.CharField(max_length=100, blank=True, default='')
timestamp = models.DateTimeField(default= timezone.now)
class Meta:
ordering = ['timestamp']
class Profile(models.Model):
# choices [...]
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
profile_type = models.CharField(max_length=2,choices=PROFILE_CHOICES,default=TEAMMEMBER)
authentication_token = models.CharField(max_length=100, null=True)
avatar_url = models.CharField(max_length=100, default='')
permissions = models.CharField(max_length=100, null=True)
timestamp = models.DateTimeField(default= timezone.now)
class Meta:
ordering = ['timestamp']
serializer.py
class UserSerlializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['name', 'email', 'password']
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerlializer()
class Meta:
model = Profile
fields = ['user', 'profile_type']
def create(self, validated_data):
user_data = validated_data.pop('user')
profile = Profile.objects.create(**validated_data)
Profile.objects.create(user=profile, **user_data)
return Profile
POST
{
"profile_type" : "ST",
"user": {
"name" : "test",
"email" : "test#test.com",
"password" : "123456"
}
}
You are creating instances in wrong way. Change your create(...) method as,
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerlializer()
class Meta:
model = Profile
fields = ['user', 'profile_type']
def create(self, validated_data):
user_data = validated_data.pop('user')
user_instance = User.objects.create(**user_data)
profile_instance = Profile.objects.create(user=user_instance, **validated_data)
return profile_instance
Profile.user should beUser instance, but you are assigning Profile instance.
Change your create method to this:
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerlializer()
class Meta:
model = Profile
fields = ['user', 'profile_type']
def create(self, validated_data):
user_data = validated_data.pop('user')
profile = Profile.objects.create(**validated_data)
user = User.objects.create(**user_data) # 1. creating user
profile.user = user # 2. assigning user
profile.save() # 3. saving profile after adding user
return profile # returning Profile instance.
inherit your user model from django contrib auth module also, and make a one to one relation with profile
from django.contrib.auth.models import User
I am looking forward to build a "mother of REST" rest api with the django REST framework. Therefore I like to have all relationships represented as urls. Using Spring Boot this is pretty easy, but not so for django REST I think. Please correct me if I am wrong.
I have to models:
class User(AbstractBaseUser):
username = models.CharField(max_length=25, unique=True)
email = models.EmailField()
created_at = models.DateTimeField(auto_now_add=True)
class Group(models.Model):
admin = models.ForeignKey(User, related_name='groups', on_delete=models.PROTECT)
shared_user = models.ManyToManyField(User, related_name='shared_groups')
name = models.CharField(max_length=25)
description = models.TextField()
created_at = models.DateField(auto_now=True)
Now I like the model relation ships to be serialized like the following schema (for shortness I only show the User model as json):
{
"id":1,
"username":"user_1",
"email":"user_1#test.de",
"created_at":"2019-03-08T08:42:34.766951Z",
"_links":{
"self":"http://127.0.0.1:8000/users/1/",
"groups":"http://127.0.0.1:8000/users/1/groups"
}
}
I know I can get the self url by passing it to the fields, but I like to have it in the _links section. I tried using the serializers.SerializerMethodFieldto add the extra information. But I have no idea to generate the urls for representing the objects.
Can anyone help?
By the way the serializer:
class UserSerializer(serializers.HyperlinkedModelSerializer):
_links = serializers.SerializerMethodField('representations')
class Meta:
model = User
fields = ('id', 'username', 'email', 'created_at', 'password', '_links')
extra_kwargs = {
'password': {'write_only': True},
'groups': {'read_only': True}
}
def create(self, validated_data):
user = User(
email=validated_data['email'],
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
def representations(self, obj):
_links = dict()
_links['self'] = 'link to user'
_links['groups'] = 'hers should be the link to the groups'
return _links
I'm trying to use my api to create and update products in a bundle. I did so:
model.py
class Business(models.Model):
name = models.CharField(max_length=155)
class Product(models.Model):
business = models.ForeignKey(
Business,
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=200)
description = models.TextField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.name
class Meta:
verbose_name = "Product"
class Bundle(models.Model):
business = models.ForeignKey(
Business,
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=100)
description = models.TextField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
products = models.ManyToManyField(Product, related_name="bundles",blank=True, null=True, through="BundleProduct")
class Meta:
verbose_name = "Bundle"
def __str__(self):
return self.name
class BundleProduct(models.Model):
bundle = models.ForeignKey(Bundle, on_delete=models.CASCADE, related_name="bundleproducts")
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="bundleproducts")
number = models.IntegerField(default=1)
class Meta:
verbose_name = "Bundle of Product"
def __str__(self):
return str(self.product.name) + " do " + self.bundle.name
def get_absolute_url(self):
return reverse("BundleProduct_detail", kwargs={"pk": self.pk})
And here is my serializers.py:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = "__all__"
class BundleProductSerializer(serializers.ModelSerializer):
class Meta:
model = BundleProduct
fields = "__all__"
class BundleSerializer(serializers.ModelSerializer):
class Meta:
model = Bundle
fields = "__all__"
My viewset.py
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
model = Product
class BundleProductViewSet(viewsets.ModelViewSet):
queryset = BundleProduct.objects.all()
serializer_class = BundleProductSerializer
model = BundleProduct
class BundleViewSet(viewsets.ModelViewSet):
queryset = Bundle.objects.all()
serializer_class = BundleSerializer
model = Bundle
When I try to post some products in bundleproducts I receive "Incorrect type. Expected pk value, received list."
Reading about this error, I found some issues relating to PrimaryKeyRelatedField and SlugRelatedField. I know I need to override but I have no idea how to do it.
It's an example of how to post would works:
{
"number": 1,
"bundle": 2,
"product":
[
1,
2
]
}
After watching the video commented by Neil, I created the following method:
class BundleSerializer(
serializers.ModelSerializer
):
products = ProductSerializer(many=True)
def create(self, validated_data):
products = validated_data.pop('products')
bundle = BundleProduct.objects.create(**validated_data)
for product in products:
BundleProduct.objects.create(**product, bundle=bundle)
return Bundle
class Meta:
model = Bundle
fields = "__all__"
But doesn't work. I receive this error: "TypeError at /api/v1/bundle/
'name' is an invalid keyword argument for this function"
If you are making post via BundleSerializer you need to pass products with list of ProductSerializer data not just id since products in BundleSerializer is accepting productsSerializer data. You are getting type error 'name' is an invalid keyword argument for this function" because your validated_data contain name and BundleProduct object Does not have name field.And you are creating BundleProduct objects with validated_data.
Create bundle object and pass id of bundle object to BundleProduct object.
If you do not want to create product and just pass existing product id you need to make ListField
You need to Override get_fields and check the requests
override to_representation to return always List of ProdutSerializer Data
Override create for POST request
Override update for PUT and PATCH Request
Below is solution for POST Request
For PATCH AND PUT Request you need to override update method of ModelSerializer and handle the products accordingly.
class BundleSerializer(serializers.ModelSerializer):
def create(self, validated_data):
products = validated_data.pop('products')
bundle = Bundle.objects.create(**validated_data)
for product_id in products:
product = get_object_or_404(Product, pk=product_id)
BundleProduct.objects.create(product=product, bundle=bundle)
return bundle
class Meta:
model = Bundle
fields = "__all__"
def to_representation(self, instance):
repr = super().to_representation(instance)
repr['products'] = ProductSerializer(instance.products.all(), many=True).data
return repr
def get_fields(self):
fields = super().get_fields()
if self.context['request'].method in ['POST', "PATCH","PUT"]:
fields['products'] = serializers.ListField(
write_only=True,
child=serializers.IntegerField()
)
return fields
sample POST data to BundleSerializer
{
"products":[1,2],
"name":"Offer One",
"description":"description",
"price":1212,
"business":1
}
In my experience, if you want to update a model and a related model in one request, with DRF, the easiest way to do this is to override the "create" method of a serializer. There's a good video on this here which I used as my reference: https://www.youtube.com/watch?v=EyMFf9O6E60
The issue here is that you are posting a list to BundleProduct's product field yet it is an ForeignKey. To join Bundle to a Product, simply POST:
{
"bundle": 2,
"product" 1,
"number": 1
}
You can repeat this:
{
"bundle": 2,
"product" 4,
"number": 1
}
to add yet another product 4 to the same bundle and so on. Just make sure you do them one by one and not in a list as you had done earlier.
I'm sending data like below to my django rest framework app.
{
"email": "sa#gmail.com"
"profile": {
stripe_token: "tok_15iFaJRbriArhT",
stripe_id: "cus_5u3iksCCw",
quantity: "5"
},
"subscription": {
subscription_name: "noidea"
}
}
Based on my serializers and models I can get everything except quantity because I don't have a field called quantity on any of my models. But I would simply like to grab the value sent in the quantity field and then do something with it.
This is what I'm doing so far:
serializers.py
class UserProfilePaymentSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('stripe_id', 'subscribed','stripe_token')
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscriptions
fields = ('subscription_name',)
class PaymentSerializer (serializers.ModelSerializer):
profile = UserProfilePaymentSerializer()
subscription = UserSubscriptionSerializer()
class Meta:
model = User
fields = ('email', 'profile','subscription',)
def update (self, instance, validated_data):
profile_data = validated_data.pop('profile')
subscription_data = validated_data.pop('subscription')
print profile_data
print subscription_data
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
telephone_number = models.CharField(max_length=100)
stripe_id = models.CharField(max_length=255)
subscribed = models.BooleanField(default=False)
stripe_token = models.CharField(max_length=255)
class Subscriptions(models.Model):
user = models.ManyToManyField(User)
subscription_name = models.CharField(max_length=100)
Add quantity field to UserProfilePaymentSerializer. Then you can access quantity field in your validated data dictionary.
class UserProfilePaymentSerializer(serializers.ModelSerializer):
quantity = serializers.CharField()
class Meta:
model = UserProfile
fields = ('stripe_id', 'subscribed','stripe_token')
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscriptions
fields = ('subscription_name',)
class PaymentSerializer (serializers.ModelSerializer):
profile = UserProfilePaymentSerializer()
subscription = UserSubscriptionSerializer()
class Meta:
model = User
fields = ('email', 'profile','subscription',)
def update (self, instance, validated_data):
....
quantity = validated_data['profile']['quantity']
....