Multiple Model File Uploader Using DRF - python

I have a models.py as the following and I'm trying to make multiple upload files in one request.
and the other fields in the model i put the values in the back-end so, what i need exactly how to to send array of data (files) in one request and handle the files and create record for every single files separate?
I also read a lot and see a lot of answers, but I felt the solution depends on the case maybe because I didn't got it will
please any one can help me ?
models.py
file_name = models.FileField(upload_to='docs/', null=True, blank=True)
created_by = models.ForeignKey(User, related_name='file_created_by', blank=True, null=True,on_delete=models.DO_NOTHING)
created_date = models.DateTimeField(auto_now_add=True)
serializers.py
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = File
fields = '__all__'
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
user = kwargs['context']['request'].user
super(FileSerializer, self).__init__(many=many, *args, **kwargs)
def create(self, validated_data):
validated_data['status'] = 'in_progress'
self.context["file_name"] = self.context['request'].FILES.get("file_name")
obj = File.objects.create(**validated_data)
return obj
views.py
class FileCreateAPIView(CreateAPIView):
queryset = File.objects.all()
serializer_class = FileSerializer
permission_classes = [IsOwnerOrReadOnly]
def get_queryset(self):
return File.objects.all()
def perform_create(self, serializer):
serializer.save(created_by=self.request.user, updated_by=self.request.user)

Update file_name model field's default serializer field to serializers.ListField and update the create method in serializer to loop over the list to create multiple objects.
Example:
class FileSerializer(serializers.ModelSerializer):
file_name = serializers.ListField(
child=serializers.FileField(
max_length=100000, # length of the file name
allow_empty_file=False,
use_url=False
),
write_only=True
)
class Meta:
model = File
fields = ('created_by', 'file_name', )
def create(self, validated_data):
files = validated_data.pop("file_name")
obj = None
# can also use `bulk_create`, if too many files
for file in files:
obj = File.objects.create(file_name=file, **validated_data)
return obj
NOTE: The serializer will now not output any file_name keys, if you use it for list usecase. For that, you can add another field fname or something as a serializers.SerializerMethodField and return the value of the file name.
Example:
class TestSerializer(serializers.ModelSerializer):
test_field = serializers.SerializerMethodField()
def get_test_field(self, obj):
# since your field would be a file, so you can access `name` attribute
return obj.test_field.name
fields = ('test_field', ...rest of the fields...)

Related

Django Rest Framework: Use URL parameter in serializer

My goal is to use a URL parameter as a variable in a function and output it in the serializer. But the following solution doesnt work. Anybody know why?
The URL looks like this:
http://127.0.0.1:8000/v1/water-bodies/?from=2013-02-17&to=2013-02-18
models.py
class Application(models.Model):
"""Database models for application information"""
name = models.CharField(max_length=255)
machine_name = models.SlugField(max_length=255, allow_unicode=True)
description = models.TextField(blank=True, null=True)
indice_to_use = models.ForeignKey('Indice', on_delete=models.PROTECT, blank=True, null=True)
def __str__(self):
return self.name
views.py
class DetailView(APIView):
def get_serializer_context(self):
context = super().get_serializer_context()
context["date_from"] = self.request.query_params.get("from", None)
return context
def get(self, request, machine_name):
application = Application.objects.get(machine_name=machine_name)
serializer = OsdSerializer(application)
return Response({"Everything you need to analyzing "+application.name: serializer.data})
serializer.py
class OsdSerializer(serializers.ModelSerializer):
bands = BandSerializer(source='indice_to_use.needed_bands', many=True)
satellite = SatelliteSerializer(source='indice_to_use.satellite_to_use')
indice = IndiceSerializer(source='indice_to_use')
def get_alternate_name(self, obj):
return self.context.get('date_from')
class Meta:
model = Application
fields = ['machine_name', 'name', 'description', 'indice', 'satellite', 'bands', 'date_from', ]
We would be required to update the code at two places.
DetailView in views.py.
In this, we are updating context to include data_from. (please note, we can also access this directly in the serialzier)
class DetailView(APIView):
...
def get(self, request, machine_name):
application = Application.objects.get(machine_name=machine_name)
serializer = OsdSerializer(application, context={
"date_from": request.query_params.get("from")
})
return Response({"Everything you need to analyzing "+application.name: serializer.data})
...
OsdSerializer in the serializers.py
class OsdSerializer(serializers.ModelSerializer):
bands = BandSerializer(source='indice_to_use.needed_bands', many=True)
satellite = SatelliteSerializer(source='indice_to_use.satellite_to_use')
indice = IndiceSerializer(source='indice_to_use')
alternate_name = serializers.SerializerMethodField()
def get_alternate_name(self, obj):
return self.context.get('date_from')
class Meta:
model = Application
fields = ['machine_name', 'name', 'description', 'indice', 'satellite', 'bands', 'date_from', 'alternate_name']
Another approach would be to access the request object directly from the context of the serializer. By default, the serializer context contains the request object in it. To do so, just update the serializer as mentioned below
OsdSerializer in the serializers.py
class OsdSerializer(serializers.ModelSerializer):
bands = BandSerializer(source='indice_to_use.needed_bands', many=True)
satellite = SatelliteSerializer(source='indice_to_use.satellite_to_use')
indice = IndiceSerializer(source='indice_to_use')
alternate_name = serializers.SerializerMethodField()
def get_alternate_name(self, obj):
return self.context.get('request').query_params.get('from', None)
class Meta:
model = Application
fields = ['machine_name', 'name', 'description', 'indice', 'satellite', 'bands', 'date_from', 'alternate_name']
First you need to change from accessing the parameter from kwargs to request.GET instead.
If you have a url like this in django '/user/<int:user_id>' then you access through kwargs.
But if you send a url parameter, like this '/user/?user_id=1'. You access through the request object.
In your situation, I think rest_framework will send the request to the serializer automatically. So you can do something like this:
def get_alternate_name(self, obj):
date_from = self.request.GET.get('from')
date=self.context.get('request').parser_context.get('kwargs').get(
'edate')

Django rest framework, update object after creation

I have a DRF API that takes in the following model:
class Points(models.Model):
mission_name = models.CharField(name='MissionName',
unique=True,
max_length=255,
blank=False,
help_text="Enter the mission's name"
)
# Some irrlevant feid
url = models.URLField(help_text='Leave Empty!', default=" ")
date_added = models.DateTimeField(default=timezone.now)
class Meta:
get_latest_by = 'date_added'
And it's serializer:
from rest_framework.serializers import HyperlinkedModelSerializer
from .models import Points
class PointsSerializer(HyperlinkedModelSerializer):
class Meta:
model = Points
fields = (
'id', 'MissionName', 'GDT1Latitude', 'GDT1Longitude',
'UavLatitude', 'UavLongitude', 'UavElevation', 'Area',
'url', 'date_added'
)
And the view:
class PointsViewSet(ModelViewSet):
# Return all order by id, reversed.
queryset = Points.objects.all().order_by('-id')
serializer_class = PointsSerializer
data = queryset[0]
serialized_data = PointsSerializer(data, many=False)
points = list(serialized_data.data.values())
def retrieve(self, request, *args, **kwargs):
print(self.data)
mission_name = self.points[1]
assign_gdt = GeoPoint(lat=self.points[2], long=self.points[3])
gdt1 = [assign_gdt.get_lat(), assign_gdt.get_long()]
assign_uav = GeoPoint(lat=self.points[4], long=self.points[5], elevation=self.points[6])
uav = [assign_uav.get_lat(), assign_uav.get_long(), assign_uav.get_elevation()]
area_name = f"'{self.points[-2]}'"
main = MainApp.run(gdt1=gdt1, uav=uav, mission_name=mission_name, area=area_name)
print('file created')
return render(request, main)
I want to update the URL field of the file to contain a constant pattern and format in the end the mission_name field.
object.url = f'127.0.0.1/twosecondgdt/{mission_name}'
How can that be achieved and where should I store such code, the views.py or serializers.py?
There are several ways this could be achieved based on your requirements.
If you want to set the url upon creation even if it is not through the api, you can do it in the save method of the model itself:
class Points(models.Model):
# fields here
def save(self, **args, **kwargs):
if not self.url.strip():
# You may want to store the value of `127...` in an environment variable
self.url = f"127.0.0.1/twosecondgdt/{self.mission_name}"
super().save(*args, **kwargs)
If you want to set it through the view/serializer, you can set it in the create method of your serializer:
class PointsSerializer(HyperlinkedModelSerializer):
def create(self, validated_data):
mission_name = validated_data["mission_name"]
validated_data["url"] = f"127.0.0.1/twosecondgdt/{mission_name}"
return super().create(validated_data)
You can also override some methods in your viewset like perform_create or create

JSONField serializes as json for POST, but string for GET

There is likely a very simple problem with my code, but I've been slamming my head against this problem for a couple days and can't make any headway.
Important Packages:
Django==1.11.3
django-cors-headers==2.1.0
djangorestframework==3.7.0
drf-nested-routers==0.90.0
psycopg2==2.7.3
pycparser==2.18
Here is what is happening:
I create a model via an AJAX call
My server correctly serializes the brainstorm_data field as a json object.
Now I navigate my user to the next page and fetch the current model
For some reason, brainstorm_data is now be returned as a string. Anytime I call a GET request on this resource I always get a string representation of the JSON object.
Here is the code associated:
models.py
from django.contrib.postgres.fields import JSONField
class Adventure(TimeStampedModel,
models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey(User)
world = models.ForeignKey(World)
theme = models.ForeignKey(Theme, default=1)
brainstorm_data = JSONField()
image_src = models.CharField(max_length=400, null=True, blank=True)
sentence_summary = models.TextField(null=True, blank=True)
paragraph_summary = models.TextField(null=True, blank=True)
page_summary = models.TextField(null=True, blank=True)
outline_complete = models.BooleanField(default=False)
brainstorm_complete = models.BooleanField(default=False)
private = models.BooleanField(default=False)
def __str__(self):
return self.name
views.py
class MyAdventuresViewSet(viewsets.ModelViewSet):
queryset = Adventure.objects.all()
serializer_class = AdventureSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return Adventure.objects.filter(user=self.request.user)
def create(self, request, *args, **kwargs):
user = self.request.user
world = World.objects.filter(user=user).first()
if not world:
world = World.objects.create(name='My World', user=user,
description="This is a default world we created for your adventures",
image_src=static('worlds/images/world_placeholder.png'))
data = request.data.copy()
data['user'] = user.pk
data['world'] = world.pk
data['theme'] = 1 # default theme
data['brainstorm_data'] = default_brainstorm
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
adventure = serializer.save()
Storyboard.objects.create(adventure=adventure, raw=default_storyboard['raw'], html=default_storyboard['html'])
return JsonResponse(serializer.data)
#detail_route(methods=['post'])
def complete_outline(self, request, pk):
adventure = Adventure.objects.get(pk=self.kwargs['pk'])
complete_adventure_outline(adventure)
serializer = self.get_serializer(data=adventure)
serializer.is_valid(raise_exception=True)
return JsonResponse(serializer.data, status=200)
#detail_route(methods=['post'])
def genres(self, request, pk):
genre_names = request.data
genre_models = Genre.objects.filter(name__in=genre_names)
adventure = self.get_object()
adventure.genre_set.set(genre_models)
adventure.save()
serializer = AdventureSerializer(adventure)
return JsonResponse(serializer.data)
serializers.py
class AdventureSerializer(serializers.ModelSerializer):
genre_set = serializers.StringRelatedField(many=True, read_only=True)
character_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
location_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
storyboard = serializers.PrimaryKeyRelatedField(read_only=True)
theme = serializers.PrimaryKeyRelatedField(queryset=Theme.objects.all())
class Meta:
model = Adventure
fields = '__all__'
mixins
# this is a dictionary used to default brainstorm data each time an adventure is created
default_brainstorm = {
"nodes": [...],
"edges": [...]
}
You can override the to_internal_value and to_representation in a new serializer field to handle the return data for JSON field.
class JSONSerializerField(serializers.Field):
"""Serializer for JSONField -- required to make field writable"""
def to_internal_value(self, data):
return data
def to_representation(self, value):
return value
And in turn, you would use this Field in a serializer:
class SomeSerializer(serializers.ModelSerializer):
json_field = JSONSerializerField()
class Meta:
model = SomeModelClass
fields = ('json_field', )
This should solve your problem :)
When I originally created the columns I did it with a different json field package. The base DB columns was actually text instead of json or jsonb. Creating new columns (django json fields), migrating the data, and then shifting the data back got my database back in a consistent order.

Media files not being returned properly

models.py:
class FWVersion(models.Model):
bin_file = models.FileField(upload_to='fW_media/bin/')
date_created = models.DateTimeField(default=timezone.now)
name = models.CharField(max_length=64)
serializers.py:
class UploadFWSerializer(serializers.ModelSerializer):
class Meta:
model = FWVersion
exclude = ('date_created',)
class GetFWSerializer(serializers.ModelSerializer):
class Meta:
model = FWVersion
fields = ('name', 'bin_file',
)
views.py:
class GetFWView(viewsets.ModelViewSet):
queryset = FWVersion.objects.all()
serializer_class = serializers.GetFWSerializer
def get_object(self):
lastest_fw = FWVersion.objects.first()
return lastest_fw
class UploadFWView(mixins.CreateModelMixin,
generics.GenericAPIView):
serializer_class = serializers.UploadFWSerializer
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
I am using django-rest-framework to create APIs for uploading and retrieving the latest version of the .bin file. The APIs are working and I am able to upload a .bin file on the server. But when downloading the file again, the server returns the first uploaded file(the oldest one)even though the name, url are of the latest uploaded file. Sometimes rarely however, it returns the latest file. I have checked the server files, they are being uploaded properly. Is this because of some caching by Django or am I missing something? Please help.
Your code pretty clearly calls first() on the queryset in GetFWView, so I'm not sure why this behaviour surprises you. If you want to always return the latest one, then you should do that:
def get_object(self):
lastest_fw = FWVersion.objects.latest('date_created')
As an aside, you shouldn't be using a ViewSet for that view; it only supports one operation, which is GET, so you should just use a RetrieveAPIView.
def get_attachment_path(instance, filename):
ext = os.path.splitext(filename)[1]
d = os.path.dirname(filename)
current_time = datetime.now().strftime('%Y%m%d-%H%M%S-%f')[:-3]
filename = os.path.join(d, current_time + ext)
return 'fW_media/bin/{}'.format(filename)
class FWVersion(models.Model):
bin_file = models.FileField(upload_to=get_attachment_path)
name = models.CharField(max_length=64)
create_time = models.DateTimeField(auto_now_add=True)
class GetFWView(viewsets.ModelViewSet):
queryset = FWVersion.objects.all()
serializer_class = GetFWSerializer
def create(self, request, *args, **kwargs):
serializer = UploadFWSerializer(data=request.data)
if serializer.is_valid():
instance = serializer.create(serializer.validated_data)
return Response('upload success')
else:
return Response(serializer.errors)
#list_route(methods=['GET'])
def get_latest(self, request):
lastest_fw = FWVersion.objects.latest('create_time')
serializer = self.get_serializer(lastest_fw)
return Response(serializer.data)

Django serializer Imagefield to get full URL

I am beginner to Django and currently, I can construct model like this.
models.py
class Car(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=5, decimal_places=2)
photo = models.ImageField(upload_to='cars')
serializers.py
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ('id','name','price', 'photo')
views.py
class CarView(APIView):
permission_classes = ()
def get(self, request):
car = Car.objects.all()
serializer = CarSerializer(car)
return Response(serializer.data)
For photo, it doesn't show full URL. How can I show full URL?
Django is not providing an absolute URL to the image stored in a models.ImageField (at least if you don't include the domain name in the MEDIA_URL; including the domain is not recommended, except of you are hosting your media files on a different server (e.g. aws)).
However, you can modify your serializer to return the absolute URL of your photo by using a custom serializers.SerializerMethodField. In this case, your serializer needs to be changed as follows:
class CarSerializer(serializers.ModelSerializer):
photo_url = serializers.SerializerMethodField()
class Meta:
model = Car
fields = ('id','name','price', 'photo_url')
def get_photo_url(self, car):
request = self.context.get('request')
photo_url = car.photo.url
return request.build_absolute_uri(photo_url)
Also make sure that you have set Django's MEDIA_ROOTand MEDIA_URL parameters and that you can access a photo via your browser http://localhost:8000/path/to/your/image.jpg.
As piling pointed out, you need to add the request while initialising the serializer in your views.py:
def my_view(request):
…
car_serializer = CarSerializer(car, context={"request": request})
car_serializer.data
For future visitors, there is no need to add another field to the serializer if the view method already returns a serialized object. The only thing required is to add the context since it is needed to generate hyperlinks, as stated in the drf documentation
#list_route()
def my_view(self, request):
qs = Object.objects.all()
return Response(MySerializer(qs, many=True, context={'request': request}).data)
Serializer class
class CarSerializer(serializers.ModelSerializer):
photo_url = serializers.ImageField(max_length=None, use_url=True, allow_null=True, required=False)
class Meta:
model = Car
fields = ('id','name','price', 'photo_url')
View
class CarView(APIView):
def get(self, request, *args, **kwargs):
queryset = Car.objects.all()
serializer = CarSerializer(queryset, many=True, context={"request":request})
return Response(serializer.data, status=status.HTTP_200_OK)
It's better to use this code, due to the above code doesn't check the image is null able or not.
class CarSerializer(serializers.ModelSerializer):
photo_url = serializers.SerializerMethodField()
class Meta:
model = Car
fields = ('id','name','price', 'photo_url')
def get_photo_url(self, car):
request = self.context.get('request')
if photo and hasattr(photo, 'url'):
photo_url = car.photo.url
return request.build_absolute_uri(photo_url)
else:
return None
serializers.py
class BannerSerializer(serializers.ModelSerializer):
image = serializers.SerializerMethodField()
def get_image(self, obj):
return self.context['request'].build_absolute_uri( obj.image.url)
views.py
banner = Banner.objects.all()
banner_data = BannerSerializer(banner,many=True, context={'request': request})
data = banner_data.data
return Response({"data":data})
I read the implement of the Serializer, and find the simplest way is to extends ImageField:
from django.db import models
class ImageField(models.ImageField):
def value_to_string(self, obj): # obj is Model instance, in this case, obj is 'Class'
return obj.fig.url # not return self.url
class Class(models.Model):
name = models.CharField(max_length=50)
intro = models.CharField(max_length=200)
# fig = models.ImageField(upload_to="classes")
fig = ImageField(upload_to="classes")
def __str__(self):
return repr(self,"name")

Categories

Resources