clearing and seeding database from endpoint in django - django

++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?

Related

ManyToMany django field not working, not taking any input in POST request?

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.
But when I define it in my module/models.py file, it is not taking any input as rooms. here are my files -
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)
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)
is_deleted = models.BooleanField(default=False)
module/serializers.py -
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(read_only=True, many=True)
class Meta:
model = Module
fields = "__all__"
module/views.py -
class add_module(APIView):
def post(self, request, format=None):
module_serializer = ModuleSerializer(data=request.data)
if module_serializer.is_valid():
module_serializer.save()
return Response(module_serializer.data['module_id'], status = status.HTTP_201_CREATED)
return Response(module_serializer.errors, status = status.HTTP_400_BAD_REQUEST)
POST request body for creating a module in POSTMAN -
{
"rooms": [
{
"room_id": 2,
"title": "4",
"desc": "22",
"level": "2",
}
],
"title": "4",
"desc": "22",
}
With this request, module is being created, but no room is getting added in it.
Can someone tell me why my rooms are not getting added while creating modules?
You need to write an explicit serializer create method to support writable nested representations.
https://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations
Example:
Given a RoomSerializer like this:
class RoomSerializer(serializers.ModelSerializer):
room_id = serializers.IntegerField()
class Meta:
model = models.Rooms
fields = '__all__'
We explicitly set room_id in RoomSerializer, to avoid <room_id> stripped away from the input data, and a MultipleObjectsReturned raised.
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(many=True) # remove *read_only=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, created = Rooms.objects.get_or_create(**data)
module.rooms.add(room)
return module

Why does my "id" field disappear in my serializer (manytomany)?

I have a "PriceTable" object that can contain several "PriceLine" objects with a manytomany relationship.
I use django rest framework to publish an api and I would like that when I use PUT or PATCH on PriceTable, I can also modify the content of PriceLine.
The goal is to have a unique UPDATE method to be able to modify the instances of the 2 objects.
My models:
class PriceTable(models.Model):
name = models.CharField(_('Name'), max_length=255)
client = models.ForeignKey(
"client.Client",
verbose_name=_('client'),
related_name="price_table_client",
on_delete=models.CASCADE
)
lines = models.ManyToManyField(
'price.PriceLine',
verbose_name=_('lines'),
related_name="price_table_lines",
blank=True,
)
standard = models.BooleanField(_('Standard'), default=False)
class Meta:
verbose_name = _("price table")
verbose_name_plural = _("price tables")
def __str__(self):
return self.name
class PriceLine(models.Model):
price = models.FloatField(_('Price'))
product = models.ForeignKey(
"client.Product",
verbose_name=_('product'),
related_name="price_line_product",
on_delete=models.CASCADE,
)
class Meta:
verbose_name = _("price line")
verbose_name_plural = _("price line")
def __str__(self):
return f"{self.product.name} : {self.price} €"
I want to be able to send a JSON of this format to modify both the table and its lines:
{
"id": 16,
"lines": [
{
"id": 1,
"price": 20.0,
"product": 1
},
{
"id": 2,
"price": 45.0,
"product": 2
}
],
"name": "test"
}
For this, I try to override the update method of my serializer:
class PriceTableSerializer(serializers.ModelSerializer):
"""
PriceTableSerializer
"""
lines = PriceLineSerializerTest(many=True)
class Meta:
model = PriceTable
exclude = ['standard', 'client',]
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
lines = validated_data.get('lines')
print(lines)
# not python code
# for target_line in lines:
# if instance.id == target_line.id
# instance.price = target_line.price
# ...
return instance
The 5 commented lines, are the logic I would like to implement.
I want to browse the received array of rows and if the id of this row is equal to the id of a row in my instance, I change the values of this row.
The problem is that the id disappears. When I print the lines variable, I get this:
[OrderedDict([('price', 20.0), ('product', <Product: Test import2>)])]
OrderedDict([('price', 20.0), ('product', <Product: Test import2>)])
What happened to the id?
As docs says in https://www.django-rest-framework.org/api-guide/serializers/#customizing-multiple-update:
You will need to add an explicit id field to the instance serializer. The default implicitly-generated id field is marked as read_only. This causes it to be removed on updates. Once you declare it explicitly, it will be available in the list serializer's update method.
So you should declare id yourself for being able to use that:
class PriceLineSerializerTest(serializers.ModelSerializer):
id = serializers.IntegerField()
class Meta:
model = PriceLine
exclude = ['id', ...]

form-data not working for ManyToMany field django

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

DRF Serializer for Writable Nested JSON

Firstly, I would like to mention that I've already seen many SO posts about this issue, but still couldn't figure out the problem occurring here.
I would like to POST a JSON object to my Django application, which looks like this:
{
"id": "1363362773409783808",
"text": "#MsLaydeeLala Damn haha. Reminds me of my dog. If someone comes into my room while I\u2019m sleeping he gets up and growls at them if he doesn\u2019t recognize them right away.",
"created_at": "2021-02-21T05:39:49.000Z",
"author": {
"id": "112233445566778899",
"username": "Elrafa559",
"name": "EL RAFA"
},
"keywords": [
{
"name": "dog"
}
]
}
and save it to my database, where my models are:
class Keyword(models.Model):
"""
Represents keywords which we are looking for, must be unique, 20 chars max.
"""
name = models.CharField(max_length=20, unique=True)
def __str__(self):
return self.name
class TwitterAccount(models.Model):
id = models.CharField(max_length=20, primary_key=True)
username = models.CharField(max_length=80)
name = models.CharField(max_length=80)
class Tweet(models.Model):
id = models.CharField(max_length=20, primary_key=True)
text = models.CharField(max_length=380) # 280 for tweet, 100 for links
created_at = models.DateTimeField()
author = models.ForeignKey(TwitterAccount, on_delete=models.CASCADE)
keywords = models.ManyToManyField(Keyword)
def __str__(self):
return str(self.author.username) + "/" + str(self.id)
class TweetKeyword(models.Model):
# TODO is CASCADE the right choice?
tweet = models.ForeignKey(Tweet, on_delete=models.CASCADE)
keyword = models.ForeignKey(Keyword, on_delete=models.CASCADE)
for that, I've written this serializer:
class TweetSerializer(serializers.ModelSerializer):
author = TwitterAccountSerializer()
keywords = KeywordSerializer(many=True)
class Meta:
model = Tweet
fields = ["id", "text", "created_at", "author", "keywords"]
# depth = 1
def create(self, validated_data):
print(validated_data)
try:
author = validated_data.pop('author')
author, did_create = TwitterAccount.objects.update_or_create(**author)
print('644444')
tweet = Tweet.objects.update_or_create(author=author, **validated_data)
keywords_data = validated_data.pop('keywords')
for keyword_data in keywords_data:
Keyword.objects.update_or_create(**keyword_data)
return tweet
except Exception as e:
print("Exception occured in method .create in TweetSerializer")
print(e.args)
return None
the error I'm getting in the method create of TweetSerializer is:
("Field 'id' expected a number but got [OrderedDict([('name', 'dog')])].",)
('`create()` did not return an object instance.',)
could anyone explain why the field keywords is getting treated as if it is id?
One way to do what you want is to:
pop keywords data from the validated data
create the tweet object without the keywords
in a for loop create each keyword, and associate it with the tweet object
Here is the code (I have removed the author part):
def create(self, validated_data):
keywords_data = validated_data.pop("keywords")
tweet = Tweet.objects.update_or_create(**validated_data)[0]
for keyword_data in keywords_data:
keyword = Keyword.objects.update_or_create(**keyword_data)[0]
tweet.keywords.add(keyword)
return tweet
You can read the DRF documentation about that, there is not that precise example but there are others and a lot of explanation.

Django - Saving in multiple models from single API

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()
...