I have two models, "Blog_model" and "File_model" where "blog_id" of "Blog_model" is the foreign key for "File_Model". The concept is to save multiple files for a single blog. Here is the model structure for reference.
class Blog_model(models.Model):
type = models.CharField(max_length = 50, default = "FOOD")
count = models.PositiveIntegerField(default = 0)
title = models.CharField(max_length = 500, unique = True)
substance = models.CharField(max_length = 5000, default = "")
thumbnail = models.ImageField(upload_to = get_media_file_name, default = "")
text = models.TextField()
create_time = models.DateTimeField(auto_now_add = True)
update_time = models.DateTimeField(auto_now = True)
class File_model(models.Model):
blog_id = models.ForeignKey(Blog_model, on_delete = models.CASCADE)
file_name = models.FileField(upload_to = get_media_file_name)
upload_time = models.DateTimeField(auto_now_add = True)
def __str__(self):
return str(self.file_name)
Now, I want to create a new blog using a single API that will have details of blogs, as well as file names. I am imagining the API structure something like -
{
"type": "FOOD",
"title": "Some Blog",
"Substance": "Some blog about food",
"text": "This is some blog about food",
"thumbnail": <InMemoryUploadedFile: Capture.PNG (image/png)>
"files": [<InMemoryUploadedFile: food1.jpg (image/jpeg)>, <InMemoryUploadedFile: food2.jpg (image/jpeg)>, <InMemoryUploadedFile: food3.jpg (image/jpeg)>]
}
Please suggest how to achieve the goal.
You may suggest a correct API structure also if above mentioned seems to be wrong.
Any suggestion is appreciated.
This is the serializer and view I am using for this purpose.
-----------------------------------
serializers.py
-----------------------------------
class File_modelCreateSerializer(serializers.ModelSerializer):
# upload_time = serializers.DateTimeField(format = date_time_format)
class Meta:
model = File_model
fields = ["file_name"]
class Blog_modelCreateSerializer(serializers.ModelSerializer):
files = File_modelCreateSerializer(many = True, required = False)
class Meta:
model = Blog_model
fields = ["type", "title", "substance", "thumbnail", "text", "files"]
def create(self, validated_data):
# files = validated_data.pop("files") # Getting no key named "files" in validated_data
new_blog = Blog_model.objects.create(**validated_data)
# for f in files:
# File_model.objects.create(blog_id = new_blog, **f)
return new_blog
-----------------------------------
views.py
-----------------------------------
# class Blog_modelCreateView(generics.CreateAPIView):
# serializer_class = Blog_modelCreateSerializer
class Blog_modelCreateView(APIView):
parser_classes = (MultiPartParser, FormParser)
def post(self, request, *args, **kwargs):
blog_serializer = Blog_modelCreateSerializer(data = request.data)
if blog_serializer.is_valid():
blog_serializer.save()
return Response(blog_serializer.data)
else:
return Response(blog_serializer.errors)
Actually, View and Serializer are linked to a model.
But, you can use #action decorator.
See Django REST Framework: Routing for extra actions
If you want to link File serializer to Blog, try this.
class BlogViewSet(ModelViewSet):
def get_serializer(self):
if self.action == 'files':
return FileSerializer
...
#action(url_path='files')
def file(self):
qs = File.objects.all()
...
Related
I've a model:
class ListingPrice(Timestamps):
price = models.ForeignKey("Price", on_delete=models.CASCADE)
location = models.ForeignKey("location", on_delete=models.CASCADE)
class Meta:
unique_together = ["price", "location"]
class Price(Timestamps):
package = models.ForeignKey("products.Package", on_delete=models.CASCADE)
locations = models.ManyToManyField("location", through="ListingPrice")
price = models.DecimalField(max_digits=11, decimal_places=3)
with a serializer:
class LocationSerializer(serializers.ModelSerializer):
name = LocalizedField()
class Meta:
model = location
fields = ['id', 'name']
class PriceSerializer(serializers.ModelSerializer):
locations = LocationSerializer(many=True, read_only=True)
class Meta:
model = Price
fields = ['package', 'locations', 'price']
def create(self, validated_data):
print("validated_data, validated_data)
and viewset:
class PriceViewSet(ModelViewSet):
queryset = Price.objects.all()
serializer_class = PriceSerializer
ordering = ['id']
permissions = {
"GET": ["view_minimum_listing_price", ],
"POST": ["add_minimum_listing_price", ],
'PUT': ['update_minimum_listing_price', ],
'DELETE': ['delete_minimum_listing_price', ],
}
In testing I'mm using the following:
data = {
"price": 12,
"package": self.package.id,
"is_enabled": False,
"location": self.location
}
response = self.client.post(path=self.url, data=data, format='json')
locations doesn't appear in validated_data?
How to get it to assign locations to the instance with post requests?
I also tried to send it with as ids list, but non works. I only field price, package, is_enabled in validated}_data, but location doesn't appear!
read_only=True means the field will be neglected in request body and will only appear in response body
locations = LocationSerializer(many=True, read_only=True)
so, remove it and you can access locations in validated_data
I have 2 models - Module and Room. A module can have zero or multiple rooms and a room can be added into multiple modules. So, there is a simple many-to-many relationship between them.
In post request, raw-data input works, but not form-data.
module/models.py -
class Module(models.Model):
module_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
rooms = models.ManyToManyField(Rooms, blank=True)
room_list = models.CharField(max_length = 100, blank=True)
rooms/models.py -
class Rooms(models.Model):
room_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
level = models.CharField(max_length=100)
module/serializers.py -
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializerWrite(many=True)
class Meta:
model = Module
fields = '__all__'
def create(self, validated_data):
rooms_data = validated_data.pop('rooms')
module = Module.objects.create(**validated_data)
for data in rooms_data:
room = Rooms.objects.get(**data)
module.rooms.add(room)
return module
def update(self, instance, validated_data):
# Updating rooms
rooms_data = validated_data.get('rooms')
instance.rooms.clear()
for room_data in rooms_data:
room = Rooms.objects.get(**room_data)
instance.rooms.add(room)
# Updating other fields
fields = [
'title',
'desc',
'thumbnail',
'is_deleted',
]
for field in fields:
setattr(instance, field, validated_data[field])
instance.save()
return instance
rooms/serialier.py -
class RoomSerialize(serializers.ModelSerializer):
room_id = serializers.IntegerField()
class Meta:
model = Rooms
fields = "__all__"
module/views.py -
class add_module(APIView):
def post(self, request, format=None):
# Adding the rooms to module from room_list
new_request = request.data.copy()
room_list=[]
if 'room_list' in new_request:
room_list_data = list(new_request['room_list'].split(" "))
for room in room_list_data:
room_object = Rooms.objects.get(room_id=room)
room_serializer = RoomSerializer(room_object)
room_list.append(room_serializer.data)
new_request.update({'rooms':room_list})
# creating the module
module_serializer = ModuleSerializer(data=new_request)
if module_serializer.is_valid():
module_serializer.save()
return Response(module_serializer.data['module_id'])
return Response(module_serializer.errors)
POST request body for updating a module in POSTMAN -
{
"module_id": 2,
"room_list": "1 2",
"title": "4",
"desc": "22",
}
Pls notice that while taking input of ManyToMany field - "rooms", I'm taking a string "room_list" as input that contains all the room_ids to be included.
This works perfectly fine when I take input as raw-data in postman, but when I use form-data, it shows -
{
"rooms": [
"This field is required."
]
}
What to do?
Nested serializers don't work with multipart/form-data.
Please refer to the following issues:
https://github.com/encode/django-rest-framework/issues/7650
https://github.com/encode/django-rest-framework/issues/7262
I am pretty new to DRF/Django and want to create an endpoint that returns nested json from multiple models in the format:
{
"site": {
"uuid": "99cba2b8-ddb0-11eb-bd58-237a8c3c3fe6",
"domain_name": "hello.org"
},
"status": "live",
"configuration": {
"secrets": [
{
"name": "SEGMENT_KEY", # Configuration.name
"value": [...] # Configuration.value
},
{
"name": "CONFIG_KEY",
"value": [...]
},
"admin_settings": {
'tier'='trail',
'subscription_ends'='some date',
'features'=[]
}
Here are the models:
class Site(models.Model):
uuid = models.UUIDField(
default=uuid.uuid4,
editable=False,
unique=True)
domain_name = models.CharField(max_length=255, unique=True)
created = models.DateTimeField(editable=False, auto_now_add=True)
modified = models.DateTimeField(editable=False, auto_now=True)
class AdminConfiguration(models.Model):
TRIAL = 'trial'
PRO = 'pro'
TIERS = [
(TRIAL, 'Trial'),
(PRO, 'Professional'),
]
site = models.OneToOneField(
Site,
null=False,
blank=False,
on_delete=models.CASCADE)
tier = models.CharField(
max_length=255,
choices=TIERS,
default=TRIAL)
subscription_ends = models.DateTimeField(
default=set_default_expiration)
features = models.JSONField(default=list)
class Configuration(models.Model):
CSS = 'css'
SECRET = 'secret'
TYPES = [
(CSS, 'css'),
(SECRET, 'secret')
]
LIVE = 'live'
DRAFT = 'draft'
STATUSES = [
(LIVE, 'Live'),
(DRAFT, 'Draft'),
]
site = models.ForeignKey(Site, on_delete=models.CASCADE)
name = models.CharField(max_length=255, blank=False)
type = models.CharField(
max_length=255,
choices=TYPES)
value = models.JSONField(
null=True)
status = models.CharField(
max_length=20,
choices=STATUSES)
Logic behind serializer/viewset to achieve mentioned json:
retrieves lookup_field: uuid
filters query param: Configuration.status (either live or draft
filters AdminConfiguration on site id (something like AdminConfiguration.objects.get(Site.objects.get(uuid))
filters Configuration on type = secret
Here are my serializers:
class SiteSerializer(serializers.ModelSerializer):
class Meta:
model = Site
fields = [
'uuid',
'domain_name'
]
class AdminSerializer(serializers.ModelSerializer):
class Meta:
model = AdminConfiguration
fields = [
'tier',
'subscription_ends',
'features'
]
class ConfigurationSubSerializer(serializers.ModelSerializer):
class Meta:
model = Configuration
fields = [
'name',
'value',
]
class SecretsConfigSerializer(serializers.ModelSerializer):
site = SiteSerializer()
admin_settings = AdminSerializer()
status = serializers.CharField()
configuration = ConfigurationSubSerializer(many=True, source='get_secret_config')
class Meta:
model = Configuration
fields = [
'site',
'admin_settings',
'status'
'configuration'
]
def get_secret_config(self, uuid):
site = Site.objects.get(uuid=self.context['uuid'])
if self.context['status'] == 'live' or self.context['status'] == 'draft':
return Configuration.objects.filter(
site=site,
status=self.context['status'],
type='secret'
)
Viewset:
class SecretsViewSet(viewsets.ReadOnlyModelViewSet):
model = Site
lookup_field = 'uuid'
serializer_class = SecertsConfigSerializer
filter_backends = (DjangoFilterBackend,)
filterset_fields = ['status'] #query params
def get_serializer_context(self):
return {
'status': self.request.GET['status'],
'uuid': self.request.GET['uuid']
}
def get_serializer(self, *args, **kwargs):
kwargs['context'] = self.get_serializer_context()
return CombinedConfigSerializer(*args, **kwargs)
What am I missing to achieve the desired output?
output from django shell:
from site_config.models import Site, AdminConfiguration, Configuration
from site_config.serializers import SecretsConfigSerializer
site = Site.objects.get(id=2)
s = SecretsConfigSerializer(site)
s.data
### OUTPUT ###
AttributeError: Got AttributeError when attempting to get a value for field `site` on serializer `SecretsConfigSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Site` instance.
Original exception text was: 'Site' object has no attribute 'site'.
Why you don't try something more general and build your response separating the serializers like this (maybe you can use the same serializers in somewhere else):
def get(self, request, *args, **kwargs):
resp = {
'site': None,
'status': None,
'configuration': None,
'admin_settings': None,
}
sites = models.Site.objects.all()
resp['site'] = serializers.SitesSerializer(sites, many=True).data
admin_settings = models.AdminConfiguration.objects.all()
resp['admin_settings'] = serializers.AdminConfigurationSerializer(admin_settings, many=True).data
# and so
return Response(resp, status=status.HTTP_200_OK)
** You can try like this. This will also help to findout errors**
def get(self, request, *args, **kwargs):
resp = {
"site": None,
"status": None,
"configuration": None,
"admin_settings": None
}
sites = models.Site.objects.all()
resp['site'] = serializers.SitesSerializer(sites, many=True).data
if resp['site'].is_valid():
admin_settings = models.AdminConfiguration.objects.all()
resp['admin_settings'] = serializers.AdminConfigurationSerializer(admin_settings, many=True).data
if resp['admin_settings'].is_valid():
return Response(resp, status=status.HTTP_200_OK)
return Response(resp['admin_settings'].errors, status=status.HTTP_404_NOT_FOUND)
return Response(resp['site'].errors, status=status.HTTP_404_NOT_FOUND)
++updated with screen shot of server error++
I'm trying to set up a rest API that can be cleared and then have the data in my postgres db re-seeded via an endpoint. I'm doing this with Django with json data in a fixtures file, executing a series of functions in my views. This has worked great so far, but now I'm trying to add data with foreign key fields.
models.py:
class Profile(models.Model):
name = models.CharField(max_length = 100)
profile_name = models.CharField(max_length = 100)
email = models.CharField(max_length = 100)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length = 250)
body = models.TextField(max_length = 4000)
profile = models.ForeignKey(Profile, on_delete = models.CASCADE)
def __str__(self):
return self.title
class Comment(models.Model):
title = models.CharField(max_length = 200)
body = models.TextField(max_length = 1000)
profile = models.ForeignKey(Profile, on_delete = models.CASCADE)
post = models.ForeignKey(Post, on_delete = models.CASCADE)
def __str__(self):
return self.title
in views.py
def seed(request):
Profile.objects.all().delete()
reset(Profile)
for profile in all_profiles:
add_profile(profile)
Post.objects.all().delete()
reset(Post)
for post in all_posts:
add_post(post)
Comment.objects.all().delete()
reset(Comment)
for comment in all_comments:
add_comment(comment)
return HttpResponse('database cleared and seeded')
def add_profile(new_profile):
profile_instance = Profile.objects.create(**new_profile)
profile_instance.save()
def add_post(new_post):
post_instance = Post.objects.create(**new_post)
post_instance.save()
def add_comment(new_comment):
comment_instance = Comment.objects.create(**new_comment)
comment_instance.save()
def reset(table):
sequence_sql = connection.ops.sequence_reset_sql(no_style(), [table])
with connection.cursor() as cursor:
for sql in sequence_sql:
cursor.execute(sql)
some example seed objects:
all_profiles = [
{
"name": "Robert Fitzgerald Diggs",
"profile_name": "RZA",
"email": "abbotofthewu#wutang.com"
}
]
all_posts = [
{
"title": "Bring da Ruckus",
"body": "some text",
"profile": 5
}
]
all_comments = [
{
"title": "famous dart",
"body": "Ghostface catch the blast of a hype verse My Glock burst",
"profile": 6,
"post": 1
}
]
Now when I hit my endpoint I get an error like "ValueError: Cannot assign "5": "Post.profile" must be a "Profile" instance." I assume this means that the integer "5" in this case is just a number and not viewed as a reference to anything, but I'm not sure what to do about it. I thought creating the model instance would take care of this.
Here is my error from the CLI:
screenshot of server error
Any ideas?
got it figured. creating an object from the model simply saves the foreign key reference as an integer, but the database needs a full instance, so I had to query the database for the foreign object in question, variable-ize it and write over the integer with the variable before saving.
new functions from views:
def add_profile(new_profile):
profile_instance = Profile.objects.create(**new_profile)
profile_instance.save()
def add_post(new_post):
found_profile = Profile.objects.get(id=new_post['profile'])
new_post['profile'] = found_profile
post_instance = Post.objects.create(**new_post)
post_instance.save()
def add_comment(new_comment):
found_profile = Profile.objects.get(id=new_comment['profile'])
found_post = Post.objects.get(id=new_comment['post'])
new_comment['profile'] = found_profile
new_comment['post'] = found_post
comment_instance = Comment.objects.create(**new_comment)
comment_instance.save()
Perhaps try loading a string instead of an integer? As in "6" instead of 6?
So i am trying to serialize multiple joined tables with django serializers. I cant find a way to do this. The query being executed is raw sql.
The models are as below
class UserDetail(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE)
mobile_number = models.IntegerField()
national_id = models.CharField(max_length = 30)
date_created = models.DateTimeField(auto_now_add = True)
address = models.CharField(max_length = 250)
merchant_name = models.CharField(null = True, max_length = 30)
class Account(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE)
account_number = models.BigIntegerField()
balance = models.FloatField()
account_type = models.ForeignKey(AccountType, on_delete = models.CASCADE)
The json for the expected result should as below
{
"userdetail": {
"mobile_number":""
},
"account": {
"account_number":""
},
"user": {
"first_name": "",
"last_name": "",
"email":""
}
}
The raw sql query is as below
queryset = Account.objects.raw('''SELECT auth_user.first_name,
auth_user.id,
auth_user.last_name,
auth_user.email,
authentication_userdetail.mobile_number,
authentication_account.account_number
FROM
public.auth_user,
public.authentication_account,
public.authentication_userdetail
WHERE
auth_user.id = authentication_userdetail.user_id
AND
auth_user.id = authentication_account.user_id
''')
If there is an alternative way to do this without using raw sql i would greatly appreciate it as im not a fan of executing raw sql queries with django ORM
Tried working with this solution but i cant seem to understand the way the queryset was serialized
Cross-table serialization Django REST Framework
Edited
class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = UserDetail
fields = ('mobile_number',)
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ('account_number',)
class AccountInfoSerializer(serializers.ModelSerializer):
user_detail = UserDetailSerializer()
account = AccountSerializer()
user = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('user_detail', 'account', 'user')
def get_user(self, obj):
return {
'first_name': 'obj.first_name',
'last_name': 'obj.last_name',
'email': 'obj.email',
}
Code for the view
serializer_class = AccountInfoSerializer
def get_queryset(self, *args, ** kwargs):
user_id = self.request.query_params.get('user_id', None)
queryset = None
if user_id is not '':
queryset = UserDetail.objects.raw()
return queryset
you can try such solution:
from rest_framework import serializers
class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = UserDetail
fields = ('mobile_number',)
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ('account_number',)
class UserSerializer(serializers.ModelSerializer):
userdetail = UserDetailSerializer()
account = AccountSerializer()
user = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('userdetail', 'account', 'user')
def get_user(self, obj):
return {
'first_name': 'obj.first_name',
'last_name': 'obj.last_name',
'email': 'obj.email',
}