Django rest framework : How to serve binary directly in the body - django

I need to return an Image (binary) directly in the body request, but what i get in response is a file generated with no extension and an empty array/json inside!
I'm using python 3, Django==1.10.5 and djangorestframework==3.5.3, drf-extensions==0.3.1 (for the nested routes) and django-extra-fields==0.9 for the ImageField.
(I have tried without django-extra-fields, it's the same)
I have already found one solution (thx a lot Enix ;p) with the base64 here :Django rest framework : How to download image with this image send directly in the body
But my boss doesn't want base64 and just want the binary inside the body response.
My models.py
class Image(models.Model):
class Meta:
verbose_name = _('Image')
verbose_name_plural = _('Images')
creation_date = models.DateTimeField(
help_text=_('Creation date'),
auto_now_add=True,
editable=False
)
modified_date = models.DateTimeField(
help_text=_('Last modification date'),
auto_now=True
)
image_file = models.ImageField(upload_to='', null=True)
My serializers.py
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('image_file',)
My views.py
class ImageViewSet(NestedViewSetMixin, viewsets.ModelViewSet):
http_method_names = ['get', 'put']
queryset = Image.objects.all()
serializer_class = ImageSerializer
pagination_class = None
def get_queryset(self, *args, **kwargs):
image = Image.objects.last() # This is just for the exemple.
filename = image.image_file
size = filename.size
response = FileResponse(open(filename.path, 'rb'), content_type="image/png")
response['Content-Length'] = size
response['Content-Disposition'] = "attachment; filename=%s" % 'notification-icon.png'
return response
I have made some test with :
django.core.files.File, filewrapper, deactivate the serializer
but have not yield desired results...
If someone find out what I'm doing wrong or maybe forgot something in the settings ? Any help regarding the same would be much appreciated!

Ok, I found my mistake...
I was trying to override get_queryset... and not the get method which doesn't work too because I need to override the list method!
I need to sleep a little bit more ;p
Here is a solution that work's great for me with Streaming for download large file if needed :
My models.py
class Image(models.Model):
class Meta:
verbose_name = _('Image')
verbose_name_plural = _('Images')
creation_date = models.DateTimeField(
help_text=_('Creation date'),
auto_now_add=True,
editable=False
)
modified_date = models.DateTimeField(
help_text=_('Last modification date'),
auto_now=True
)
image_file = models.ImageField(upload_to='', null=True)
My serializers.py
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('image_file',)
My views.py
class ImageViewSet(NestedViewSetMixin, viewsets.ModelViewSet):
http_method_names = ['get', 'put']
queryset = Image.objects.all()
serializer_class = ImageSerializer
pagination_class = None
def list(self, request, *args, **kwargs):
image = Image.objects.get(pk=get_your_good_record)
filename = image.image_file
size = filename.size
content_type_file = mimetypes.guess_type(filename.path)[0]
response = StreamingHttpResponse(open(image.image_file.path, 'rb'), content_type=content_type_file)
response['Content-Disposition'] = "attachment; filename=%s" % str(filename)
response['Content-Length'] = size
return response
So easy ;p

Related

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

Is there a way to reload ViewSet on each GET request for new data in DRF?

I am trying to generate a random object from my Model. The problem is that it will only work one time, then I have to restart the server to get a new object. It just keeps giving me the same object until the restart.
I have been looking for solution on stack overflow but haven't found any.
Views.py
def dailyaskist(category):
qs = Task.objects.filter(category=category)
max_num = len(qs)
while True:
pk = random.randint(1, max_num)
task = Task.objects.filter(pk=pk).first()
if task:
return task.pk
class DailyTaskEcommerceViewSet(viewsets.ModelViewSet):
category = 'ecommerce'
task_pk = dailyaskist(category)
queryset = Task.objects.filter(pk=task_pk)
serializer_class = TaskSerializer
serialisers.py
class StepSerializer(serializers.HyperlinkedModelSerializer):
task_id = serializers.PrimaryKeyRelatedField(queryset=Task.objects.all(), source='task.id')
class Meta:
model = Step
fields = ('title', 'description', 'done', 'task_id')
class TaskSerializer(serializers.HyperlinkedModelSerializer):
steps = StepSerializer(many=True, read_only=True)
class Meta:
model = Task
fields = ('title', 'description', 'video', 'done', 'steps')
models.py
Categories = (
('ecommerce', 'Ecommerce'),
)
class Task(models.Model):
title = models.CharField(max_length=50)
description = models.TextField(max_length=360)
video = models.CharField(max_length=30, default='')
category = models.CharField(choices=Categories, default='', max_length=30)
done = models.BooleanField(default=False)
def __str__(self):
return self.title
class Step(models.Model):
task = models.ForeignKey(Task, related_name='steps', on_delete=models.CASCADE)
title = models.CharField(max_length=50)
description = models.TextField(max_length=360)
done = models.BooleanField(default=False)
def __str__(self):
return self.title
I want to receive a new object (task) each time I make a GET request using the DailyTaskEcommerceViewSet.
Thanks in advance! :D
You would do this in a method. In this case, get_queryset seems the right place.
class DailyTaskEcommerceViewSet(viewsets.ModelViewSet):
serializer_class = TaskSerializer
category = 'ecommerce'
def get_queryset(self):
task_pk = dailyaskist(self.category)
return Task.objects.filter(pk=task_pk)

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

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

Update M2M relationship django rest framework (many=true)

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