Update M2M relationship django rest framework (many=true) - django

I try to update a channel:
PUT
content [{'url': 'http://localhost:8000/api/movies/2', 'title': u'Ariel', 'backdrop_path': u'/z2QUexmccqrvw1kDMw3R8TxAh5E.jpg', 'popularity': 0.082, 'poster_path': u'/8ld3BEg8gnynRsfj2AzbLocD8NR.jpg', 'release_date': datetime.date(1988, 10, 21), 'runtime': 69L, 'tagline': u'', 'vote_average': 9.0, 'vote_count': 0L}]
csrfmiddlewaretoken XXXXXXXXXXXXXXXXXXXXXXXXXXx
name cody private
owner http://localhost:8000/api/users/1
private 1
And I get this error:
instance should be a queryset or other iterable with many=True
And here is the code you need to understand what's going on.
class Channel(models.Model):
"""
A channel is a "container" for a users movies and television shows.
"""
PUBLIC_VISIBILITY, PRIVATE_VISIBILITY = 0, 1
VISIBILITY_CHOICES = (
(PUBLIC_VISIBILITY, 'public'),
(PRIVATE_VISIBILITY, 'private'),
)
owner = models.ForeignKey(User, related_name='owned_channels')
name = models.CharField(max_length=60)
content = models.ManyToManyField(Movie, db_table='channel_contents',
related_name='channels', null=True, blank=True, default=None)
subscribers = models.ManyToManyField(User, db_table='channel_subscribers',
related_name='subscribed_channels', null=True, blank=True, default=None)
created = models.DateTimeField(auto_now_add=True)
last_mod = models.DateTimeField(auto_now=True)
query = models.CharField(max_length=255, default='')
private = models.IntegerField(choices=VISIBILITY_CHOICES, default=PRIVATE_VISIBILITY)
default = models.BooleanField(default=False)
class Movie(models.Model):
id = models.BigIntegerField(primary_key=True)
adult = models.BooleanField()
backdrop_path = models.ImageField(upload_to='backdrop/')
budget = models.IntegerField(blank=True, null=True)
genres = models.ManyToManyField('Genre',
through='MovieGenre',
blank=True, null=True)
homepage = models.URLField(blank=True, null=True)
imdb_id = models.CharField(max_length=20, blank=True, null=True)
original_title = models.CharField(max_length=100)
overview = models.TextField(blank=True, null=True)
popularity = models.FloatField(blank=True, null=True)
poster_path = models.ImageField(upload_to='poster/')
release_date = models.DateField(blank=True, null=True)
revenue = models.IntegerField(blank=True, null=True)
runtime = models.IntegerField(blank=True, null=True)
tagline = models.CharField(max_length=200, blank=True, null=True)
title = models.CharField(max_length=100, db_index=True)
vote_average = models.FloatField(blank=True, null=True)
vote_count = models.IntegerField(blank=True, null=True)
actors = models.ManyToManyField('Actor',
through='MovieActor',
blank=True, null=True)
directors = models.ManyToManyField('Director',
through='MovieDirector',
blank=True, null=True)
production_companies = models.ManyToManyField(
'ProductionCompany',
through='MovieProduction',
blank=True, null=True)
Channel serializing code:
# Routes
url(r'^channels$', ChannelList.as_view(), name='channel-list'),
url(r'^channels/(?P<pk>\d+)$', ChannelDetail.as_view(), name='channel-detail'),
# Views
class ChannelList(generics.ListCreateAPIView):
"""
API endpoint that represents a list of users.
"""
model = Channel
serializer_class = ChannelSerializer
class ChannelDetail(generics.RetrieveUpdateDestroyAPIView):
"""
API endpoint that represents a single users.
"""
model = Channel
serializer_class = ChannelSerializer
# Serializer
class ChannelSerializer(serializers.HyperlinkedModelSerializer):
content = MovieSerializer(many=True)
class Meta:
model = Channel
fields = ('url', 'owner', 'name', 'content', 'private')

As you can read here, nested relations currently don't support write operations. Use HyperlinkedRelatedField instead or write a custom serializer, that implements the features you need.

If you want to update the nested relation you can do like this,
class SchoolSerializer(serializers.HyperlinkedModelSerializer):
students = StudentSerializer(many=True, read_only=True)
students_ids = serializers.PrimaryKeyRelatedField(many=True,\
read_only=False, queryset=Student.objects.all(),\
source='students')
class Meta:
model = School
fields = ('name', 'image', 'address', 'url',\
'students', 'students_ids')
use PrimaryKeyRelatedField this will allow you to create, update, nested relations (Many-to-Many field) by just passing a list of id's
students will give you nested data,
students_ids can be used for write operations

This is a little outdated, but for future people looking for a potential solution to this problem, I found it useful to patch viewset.
You cannot read post params twice, which is the only thing preventing one from passing a Primary key for the related update and performing the m2m update in post_save
I made a custom viewset based on ModelViewSet with updated create and update statements:
In your app, you can create a module called viewsets.py:
# -*- coding: utf-8 -*-
from rest_framework import mixins
from rest_framework import status
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
class RelatedCreateModelMixin(mixins.CreateModelMixin):
'''
Monkey patch the UpdateModel for ModelViewSet Mixin to support data
transferrance from pre - to - save - to - post
'''
def create(self, request, *args, **kwargs):
data = request.DATA
serializer = self.get_serializer(data=data, files=request.FILES)
if serializer.is_valid():
self.pre_save(serializer.object, data=data)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True, data=data)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class RelatedUpdateModelMixin(mixins.UpdateModelMixin):
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
self.object = self.get_object_or_none()
data = request.DATA
serializer = self.get_serializer(self.object, data=data,
files=request.FILES, partial=partial)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
try:
self.pre_save(serializer.object, data=data)
except ValidationError as err:
# full_clean on model instance may be called in pre_save,
# so we have to handle eventual errors.
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
if self.object is None:
self.object = serializer.save(force_insert=True)
self.post_save(self.object, data=data, created=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
self.object = serializer.save(force_update=True)
self.post_save(self.object, created=False)
return Response(serializer.data, status=status.HTTP_200_OK)
class RelatedModelViewSet(RelatedCreateModelMixin,
mixins.RetrieveModelMixin,
RelatedUpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
pass
Then, in your view, use instead:
from MYAPP import viewsets
Which allows you to do something along the lines of:
def post_save(self, obj, *args, **kwargs):
data = kwargs.get('data')
model_id = data.get('id')
parent_obj = Model.objects.get(id=model_id)
method = self.request.method
if method == 'POST':
parent_obj.m2m.add(obj)
elif method == 'PUT':
parent_obj.m2m.remove(obj)
Not the most elegant solution, but I find it preferable to writing a custom serializer

Related

How to save data from API to my MySQL database in Django REST project

I have a Django REST project. There is a model Product. I get some data from marketplace API about products stocks. And I need to save it to my datbase. I don't know, what kind of viewsets to choose. And how to make a create method. Thanks.
My Product model
`
class Product(models.Model):
store = models.ForeignKey(
Store,
on_delete=models.PROTECT, blank=True,
verbose_name="Store")
offer_id = models.CharField(max_length=50,
blank=True,
default="",
verbose_name="SKU")
name = models.CharField(max_length=128,
blank=True,
default="",
verbose_name="Name")
present = models.PositiveIntegerField(
default=0,
verbose_name="Present")
reserved = models.PositiveIntegerField(
default=0,
verbose_name="Reserved")
`
My serializer
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
store = serializers.CharField()
offer_id = serializers.CharField()
name = serializers.CharField()
present = serializers.IntegerField()
reserved = serializers.IntegerField()
The data I get from API is a list, for example:
[
{
"offer_id":"1-100-3-0",
"present":5,
"reserved":1
},
{
"offer_id":"1-101-3-9",
"present":0,
"reserved":0
}
]
Refer the doccumentation for more
here is how you do it
#api_view([ 'POST'])
def product_post(request):
if request.method == 'POST':
serializer = ProductSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
and urls should be
path('product_post/', views.product_post, name='product_post'),
I creat a Modelviewset, it works with out errors, but stocks aren't saved in DB.
I tried to create and run a test, but I have
self.assertEqual(len(Product.objects.filter(store__user_id=self.user.pk)), 1)
AssertionError: 0 != 1
So, I can't find a reason.
class ProductApi(ModelViewSet):
serializer_class = ProductSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return Product.objects.filter(
store__user_id=self.request.user.pk)\
.order_by('-offer_id')
def create(self, request, *args, **kwargs):
st = Store.objects.filter(user=self.request.user,
type=1)
for _ in st:
api = Parser(api_key=_.api_key,
client_id=_.client_id)
data = api.get_product_info_stocks()
if len(data) > 0:
for stock in data:
if Product.objects.filter(
offer_id=stock.get('offer_id')).exists():
pass
else:
stock_data = {
'offer_id': stock['offer_id'],
'present': stock['present'],
'reserved': stock['reserved']
}
Product.objects.create(**stock_data)
stocks = Product.objects.filter(
store__in=st).order_by(
'-offer_id')
s = ProductSerializer(stocks, many=True)
return Response(status=200, data=s.data)
else:
return Response(status=204,
data={"Info": "Empty response"})
return Response(status=400,
data={"Message": "User has no stores"})

serializer showing name of model

I want to serialize data from nested queryset:
I have working code but output from serializer showing too many data. I want hide this for security reason.
example output:
(...)
"gallery": "[{"model": "mainapp.imagesforgallery", "pk": 1, "fields": {"user": 1, "image": "uploads/2022/8/6/drw/Adapta-KDE-theme_JOgL4kO.webp", "thumbnail": ""}}]"
(...)
this is models.py
class ImagesForGallery(models.Model):
user = models.ForeignKey(UserProfile, null=True, blank=True, on_delete=models.CASCADE)
image = models.ImageField(upload_to=user_directory_path, blank=True, null=True)
thumbnail = models.ImageField(upload_to='uploads/', blank=True, null=True)
def __str__(self):
return 'User: {} || Image: {}'.format(self.user, self.image)
class Gallery(models.Model):
project = models.ForeignKey(Projects, null=True, blank=True, on_delete=models.CASCADE)
project_gallery = models.ManyToManyField(ImagesForGallery, blank=True, related_name='project_gallery')
def __str__(self):
return '{}'.format(self.project)
This is my view
class HomeView(viewsets.ModelViewSet):
serializer_class = ProjSerializer
queryset = Proj.objects.all()
def list(self, request, *args, **kwargs):
response = super(HomeView, self).list(request, args, kwargs)
gal = Gallery.objects.all()
for d in response.data:
for g in gal:
if d['uuid'] == str(g.project.uuid):
qs = g.project_gallery.get_queryset()
serialized_obj = serializers.serialize('json', qs)
d['gallery'] = serialized_obj
return response
This code compares the project model to the photo gallery model. If uuid is correct, include this gallery in the project and send json.
I'm not sure the code is efficient and safe. The question is how to modify the code so that it does not show the model name.
You need to use your ProjSerializer to serialize your queryset
if d['uuid'] == str(g.project.uuid):
qs = g.project_gallery.get_queryset()
serialized_obj = ProjSerializer(qs, many=True).data
d['gallery'] = serialized_obj

Always getting {"detail":"Unsupported media type \"application/json\" in request."} error when I try to post data on postman

I am working on a project that requires me to upload an image. However when I am trying to upload one and posting I ma getting the above error. I have no clue what to do anymore.
I have already tried using FileUploadParser and creating class Base64ImageField too. Please Help.
models
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE, default=None, null=True)
avatar = models.ImageField(upload_to='', blank=True, null=True)
code = models.CharField(max_length=8, unique=True, default=unique_rand)
emailVerified = models.NullBooleanField(null=True, default=None)
facebookId = models.CharField( null=True,unique=True, default=None,max_length=255)
googleId = models.CharField(null=True,unique=True,default=None,max_length=255)
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$')
mobile = models.CharField(validators=[phone_regex, MinLengthValidator(10)], max_length=10, null=True, default=None)
mobileVerified = models.NullBooleanField(null=True,default=None)
status = models.BooleanField(default=False)
serializers
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
avatar = Base64ImageField(required=False)
code = serializers.CharField(read_only=True)
serializers.FileField(use_url=False)
class Meta:
model = UserProfile
fields = '__all__'
extra_kwargs = {'user': {'required': False}}
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
image = validated_data.pop('avatar')
upr=UserProfile.objects.create(user=user,image=image,**validated_data)
return upr
views
class UserCreate(generics.ListCreateAPIView):
serializer_class = UserProfileSerializer
user_serializer = UserSerializer
queryset = UserProfile.objects.all()
parser_classes = (FormParser,MultiPartParser)
def pre_save(self, request):
request.avatar = self.request.FILES.get('file')
def post(self, request):
print(request.data)
serializer= UserProfileSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Maybe try using "multipart form" instead of JSON as your POST payload type. For the value (I am using Insomnia), select the dropdown on the right and select "File" instead of "Text". Then upload a file.
That worked for me, not sure if it's the same problem. Hope that helps!
Here's a post that has the answer you're looking for.
Posting a File and Associated Data to a RESTful WebService preferably as JSON

The `.create()` method does not support writable nested fields by default

I am new to Django REST, I was trying to make some entry to the DB using the serilaizer in django rest. But i am getting some errors while using the create method.
My models are,
class CoreInformation(models.Model):
site_name = models.CharField(max_length=145, blank=True, null=True)
status = models.CharField(max_length=45, blank=True, null=True)
created_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
class Meta:
managed = False
db_table = 'core_information'
class CoreDetailInformation(models.Model):
core_info = models.ForeignKey('CoreInformation', models.DO_NOTHING, related_name='core_base_info')
old_sac = models.CharField(max_length=45, blank=True, null=True)
msc = models.CharField(max_length=45, blank=True, null=True)
class Meta:
db_table = 'core_detail_information'
And i have two ModelSerializer like below ,
class CoreDetailSerializer(serializers.ModelSerializer):
class Meta:
model = CoreDetailInformation
fields = ('id','old_sac', 'msc')
class CustomCoreInfoSerializer(serializers.ModelSerializer):
core_base_info = CoreDetailSerializer(many=True)
class Meta:
model = CoreInformation
# fields = '__all__'
fields = ('id', 'site_name', 'status', 'created_at', 'core_base_info')
#transaction.atomic
def create(self, validated_data):
try:
with transaction.atomic():
base_info = CoreInformation.objects.create(site_name=validated_data['site_name'],status=validated_data['status']
for site_detail in validated_data['core_base_info']:
CoreDetailInformation.objects.get_or_create(msc=site_detail['msc'],old_sac=site_detail['old_sac'],core_info=base_info)
except CoreInformation.DoesNotExist as e:
raise e
except CoreDetailInformation.DoesNotExist as e:
raise e
and my views.py is ,
class CoreInformation(generics.ListCreateAPIView):
queryset = models.CoreInformation.objects.all()
serializer_class = CustomCoreInfoSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer)
serializer = self.get_serializer(instance=instance)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def perform_create(self, serializer):
return serializer.create(validated_data=serializer.validated_data)
To create the CoreInformation my input will be like below,
{
"site_name": "xxxxxxxxxx",
"status": "create",
"core_base_info": [{
"old_sac": '1',
"msc": "abc1,abc2"
},
{
"old_sac": '2',
"msc": "abc3,abc4"
}]
}
But when i am compiling its returns me the below error,
AssertionError at /api/core/
The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `src.core.serializers.CustomCoreInfoSerializer`, or set `read_only=True` on nested serializer fields.
I found this , but did n't help for me.
Any help would be greatly appreciated. Thanks.
I think that you can use this GitHub to solve your problem
Try this: https://github.com/beda-software/drf-writable-nested

What should my django serializer def Update look like?

I'm trying to add content for the first time using a DRF back-end written by someone else. I am receiving this error...
django_1 | AssertionError: The `.update()` method does not support writable nested fields by default.
django_1 | Write an explicit `.update()` method for serializer `myapp.tracker.serializers.MedicationSerializer`, or set `read_only=True` on nested serializer fields.
How can I write the Update method?
I've done rails before so I'm familiar with the concepts? So when I see a method "create" in my serilaizers.py I think "there must be a way to write a def Update here" But since I'm new to django I have NO IDEA what that method should actually look like ^_^. This is where I'm stuck.
Here is the serializers.py , models.py, and views.py code specific to the model I am trying to update...let me know if I need to post another files contents.
What should my def update method look like?
my serializer.py
class MedicationSerializer(serializers.HyperlinkedModelSerializer):
cat = CatSerializer()
class Meta:
model = Medication
fields = (
'id',
'cat',
'name',
'duration',
'frequency',
'dosage_unit',
'dosage',
'notes',
'created',
'modified',
'showRow',
)
def create(self, validated_data):
cat_data = validated_data.pop('cat')
cat_obj = Cat.objects.get(**cat_data)
medication = Medication.objects.create(cat=cat_obj, **validated_data)
return medication
the models.py looks like this
class Medication(models.Model):
cat = models.ForeignKey(Cat, blank=True, null=True)
name = models.CharField(max_length=100)
duration = models.TextField(blank=True, null=True)
frequency = models.CharField(max_length=2)
dosage_unit = models.CharField(max_length=2, default=Weight.MILLILITERS)
dosage = models.IntegerField(blank=True, null=True)
notes = models.CharField(max_length=2048, blank=True, null=True)
created = models.DateTimeField(blank=True, null=True)
modified = models.DateTimeField(blank=True, null=True)
showRow = models.BooleanField(default=True)
def save(self, *args, **kwargs):
# Save time Medication object modified and created times
self.modified = datetime.datetime.now()
if not self.created:
self.created = datetime.datetime.now()
super(Medication, self).save(*args, **kwargs)
def __str__(self):
if self.cat:
cat_name = self.cat.name
else:
cat_name = "NO CAT NAME"
return "{cat}: {timestamp}".format(cat=self.cat.name, timestamp=self.created)
and the views.py ....
class MedicationViewSet(viewsets.ModelViewSet):
queryset = Medication.objects.all()
serializer_class = MedicationSerializer
filter_fields = ('cat__slug', 'cat__name')
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
Thank you for your time.
Try adding read-only=True to your Catserializer
class MedicationSerializer(serializers.HyperlinkedModelSerializer):
cat = CatSerializer(read_only=True)
class Meta:
model = Medication
fields = (
'id',
'cat',
'name',
'duration',
'frequency',
'dosage_unit',
'dosage',
'notes',
'created',
'modified',
'showRow',
)
def create(self, validated_data):
cat_data = validated_data.pop('cat')
cat_obj = Cat.objects.get(**cat_data)
medication = Medication.objects.create(cat=cat_obj, **validated_data)
return medication