DRF serializer remove imagefield absolute url - django

In DRF, now if i save the image the serializer is giving me the image with absolute path. But in my scenario, i don't want absolute path. I just need a image path which is there in DB. In my scenario, i'm hard-coding the image path in json. So i can't use the absolute path. Can anyone suggest some ideas to implement it.

You can use SerializerMethodField. You can return the value as it is stored in the database by doing the following:
class YourSerializer(serializers.ModelSerializer):
image = serializers.SerializerMethodField()
def get_image(self, obj):
return obj.image.file.name
docs: https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

Create custom FileField will solve the issue
class CustomFileField(serializers.FileField):
def to_representation(self, value):
if not value:
return None
if not getattr(value, 'path', None):
# If the file has not been saved it may not have a path.
return None
return value.path
class MyFooSerializer(serializers.ModelSerializer):
my_file_field = CustomFileField()
...
# other code

Above answers are working for list and retrieve method, but i need to add for post and retrieve instead of querying and serializing it again. Below method will work.
Normally for ImageField and FileField will return name but in DRF it's been converted to url for usefulness. So u just need to change there that's it.
In your serailizer, just specify below like that:
Field_name = serializers.ImageField(use_url=False)
So this line will convert absolute url to name as response after Save and Update.
For further reference refer this link

Related

Updating a value in serializer after accessing .data in Django Rest Framework

I have a serializer for my Post class which has a image and a link attribute.
media is an FileField and link is a URLField which is a url to somewhere else I share my post (in another website.)
I want to:
Submit my post data (text, and the image)
Accessing the url of submitted file to use in sharing it in another place.
Updating the link value after I found it.
This is what I tried:
post = PostCreateSerializer(data=request.data, context={'request': request})
post.is_valid(raise_excpetions=True)
post.save()
media_url = post.data.get('media')
link = find_link_value(media_url)
post.link = link
post.save()
This raises an exception. says:
You cannot call `.save()` after accessing `serializer.data`.If you need to access data before committing to the database then inspect 'serializer.validated_data' instead.
The problem is when I use post.validated_data.get('media') instead of .data, it doesn't give me the url. It gives me an InMemoryUploadedFile object, that of course, doesn't have any path and url.
I thought I could use name attribute of InMemoryUploadedFile object to find the url (the one that will be created after .save()), but when the name is duplicate, the real name of file in disk and url differs from it's original name (for example, name.jpg and name_aQySbJu.jpg) and I can't use it for my purpose.
Question
How can I have access to URL of that uploaded file, and also call save() after I updated my post?
The serializer's save() method return corresponding instance. So, you can use that to get the url
post = PostCreateSerializer(data=request.data, context={'request': request})
post.is_valid(raise_excpetions=True)
post_instance = post.save()
media_url = post_instance.media.url
link = find_link_value()
post_instance.link = link
post_instance.save()
post = PostCreateSerializer(data=request.data, context={'request': request})
post.is_valid(raise_excpetions=True)
media_url = post.validated_data.get('media')
link = find_link_value()
post.save(link=link)
Actually, you can use this approach in order to save your post. When you pass link through serializer_obj.save(**kwargs) method it automatically pass your extra data into create(self, validated_data) or update(self, instance, validated_data) adding your extra data into validatad_data as dictionary. So than you can handle your extra data in create or update method of serializer.

How to check that an uploaded file is a valid Image in Django

I have an ImageField in one of my models so that users can upload an image. When a user submits an upload form, I want to verify that the file in question is a fully valid and displayable image.
I tried using PIL to verify that the image was in fact authentic, but using
from PIL import Image
Image.open(model.file)
Image.verify()
No matter what file I give it though, it always throws an exception.
Anyone know of an easy way to verify the file?
Good news, you don't need to do this:
class ImageField(upload_to=None, height_field=None, width_field=None, max_length=100, **options)
Inherits all attributes and methods from FileField, but also validates that the uploaded object is a valid image.
https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ImageField
Also, you should use verify() as follows:
from PIL import Image
im = Image.open(model.file)
im.verify()
you can use a 'Pillow' with 'try,except 'block, before insert image/data to a database or use it where you want,
like my following example for submit ticket support form , 'view.py' file :
from PIL import Image
if request.method=='POST':
# check if attachment file is not empty inside try/except to pass django error.
try:
ticket_attachmet_image = request.FILES["q_attachment_image"]
except:
ticket_attachmet_image = None
# check if uploaded image is valid (for example not video file ) .
if not ticket_attachmet_image == None:
try:
Image.open(ticket_attachmet_image)
except:
messages.warning(request, 'sorry, your image is invalid')
return redirect('your_url_name')
#done.
if 'image' in request.FILES['image'].content_type:
# Some code
else:
# the file is not image
The ImageField doesn't work when the form is not created by the form.py
In fact, if we upload a file, that is not an image, and save it in the image field, it wouldn't raise any error so, the content_type of the file must be checked before saving.

Providing parameters when reverse_lazy-ing a success_url redirect

TLDR: I want to be able to provide slug in reverse_lazy('view', kwargs={'slug':'my_page'}) like this: reverse_lazy('view').apply(kwargs={'slug':'my_page'}), after creating the lazy object.
I have the following url pattern that includes a slug to identify a page model instance:
url(r'^(?P<slug>'+settings.SLUG_PATTERN+')/$', views.MyView.as_view(), name='view'),
I have another view for editing the page:
url(r'^(?P<slug>'+settings.SLUG_PATTERN+')/_edit/$',
views.MyEditView.as_view(success_url=reverse_lazy('view')), name='edit'),
Note the addition of success_url so that when I submit the form with the new content I'm redirected to the now-edited page. In case I ever change my view url pattern I don't have to worry about updating the redirect for my edit url.
After the form is validated and saved, the view grabs the success url to be used in a HttpResponseRedirect. However just the name 'view' isn't enough to identify the URL. I also need to know the slug name which is stored in my page model's slug field.
A similar question is here: success_url in UpdateView, based on passed value
The answers suggest writing a custom get_success_url for every view, but there must be better approaches.
In the generic views in django's edit.py there's this:
url = self.success_url.format(**self.object.__dict__)
If success_url were given as a hard coded URL but with a slug identifier such as '{slug}/' this would replace it with the slug field in my model. That's very close to what I want, but I don't want to hard code my URL. This brings me to my question:
How can I pass in parameters to a reverse_lazy object? I would use this in my base view's get_success_url with self.object.__dict__ and it'd just work everywhere.
Moreover if my slug string was stored on separate Slug model I might want the success URL to be '{slug.name}/'. With the above approach I could supply a mapping between the URL parameters and model attributes:
redirect_model_mapping = {'slug': '{slug.name}'}
...
def get_success_url(self):
url = self.success_url
if is_a_lazy_redirect(url):
url = url.somehow_apply_parameters(redirect_model_mapping)
return url.format(**self.object.__dict__)
I would like somehow_apply_parameters to be equivalent to originally calling reverse_lazy('blog:view', kwargs=redirect_model_mapping). However I don't think this should be in urls.py because it shouldn't have to know about the mapping.
This is a hack, but does what I want...
class MyView(FormMixin, ...):
#this is actually set on child classes
redirect_model_mapping = {'slug':'{slug.name}'}
def get_success_url(self):
url = self.success_url
if url is not None:
if hasattr(self.success_url, '_proxy____kw'):
url_parameters = dict((k, v.format(**self.object.__dict__)) for k, v in six.iteritems(self.redirect_model_mapping))
url._proxy____kw = {'kwargs': url_parameters}
url = force_text(url)
else:
url = url.format(**self.object.__dict__)
else:
raise ImproperlyConfigured("No URL to redirect to.")
return url
It replaces the kwards parameter normally passed to reverse_lazy but after it actually has the values it needs. As reverse_lazy also requires the string to match the regex, I had to make the mapping between url parameters and the values in the models first.
I'd quite like an approach that doesn't need to write to _proxy____kw.

Getting Google App Engine blob info in Django view

This is a follow up question for Django on Google App Engine: cannot upload images
I got part of the upload of images to GAE Blobstore working. Here's what I did:
In models.py I created a model PhotoFeature:
class PhotoFeature(models.Model):
property = models.ForeignKey(
Property,
related_name = "photo_features"
)
caption = models.CharField(
max_length = 100
)
blob_key = models.CharField(
max_length = 100
)
In admin.py I created an admin entry with an override for the rendering of the change_form to allow for insert of the correct action to the Blobstore upload url:
class PhotoFeatureAdmin(admin.ModelAdmin):
list_display = ("property", "caption")
form = PhotoFeatureForm
def render_change_form(self, request, context, *args, **kwargs):
from google.appengine.ext import blobstore
if kwargs.has_key("add"):
context['blobstore_url'] = blobstore.create_upload_url('/admin/add-photo-feature')
else:
context['blobstore_url'] = blobstore.create_upload_url('/admin/update-photo-feature')
return super(PhotoFeatureAdmin, self).render_change_form(request, context, args, kwargs)
As I use standard Django, I want to use the Django views to process the result once GAE has updated the BlobStore in stead of BlobstoreUploadHandler. I created the following views (as per the render_change_form method) and updated urls.py:
def add_photo_feature(request):
def update_photo_feature(request):
This all works nicely but once I get into the view method I'm a bit lost. How do I get the Blob key from the request object so I can store it with PhotoFeature? I use standard Django, not Django non-rel. I found this related question but it appears not to contain a solution. I also inspected the request object which gets passed into the view but could not find anything relating to the blob key.
EDIT:
The Django request object contains a FILES dictionary which will give me an instance of InMemoryUploadedFile. I presume that somehow I should be able to retrieve the blob key from that...
EDIT 2:
Just to be clear: the uploaded photo appears in the Blobstore; that part works. It's just getting the key back from the Blobstore that's missing here.
EDIT 3:
As per Daniel's suggestion I added storage.py from the djangoappengine project which contains the suggested upload handler and added it to my SETTINGS.PY. This results in the following exception when trying to upload:
'BlobstoreFileUploadHandler' object has no attribute 'content_type_extra'
This is really tricky to fix. The best solution I have found is to use the file upload handler from the djangoappengine project (which is associated with django-nonrel, but does not depend on it). That should handle the required logic to put the blob key into request.FILES, as you'd expect in Django.
Edit
I'd forgotten that django-nonrel uses a patched version of Django, and one of the patches is here to add the content-type-extra field. You can replicate the functionality by subclassing the upload handler as follows:
from djangoappengine import storage
class BlobstoreFileUploadHandler(storage.BlobstoreFileUploadHandler):
"""Handler that adds blob key info to the file object."""
def new_file(self, field_name, *args, **kwargs):
# We need to re-process the POST data to get the blobkey info.
meta = self.request.META
meta['wsgi.input'].seek(0)
fields = cgi.FieldStorage(meta['wsgi.input'], environ=meta)
if field_name in fields:
current_field = fields[field_name]
self.content_type_extra = current_field.type_options
super(BlobstoreFileUploadHandler, self).new_file(field_name,
*args, **kwargs)
and reference this subclass in your settings.py rather than the original.

How do you put a file in a fixture in Django?

I can easily fill the field of a FileField or ImageField in a Django fixture with a file name, but that file doesn't exist and when I try to test my application it fails because that file doesn't exist.
How do I correctly populate a FileField or Imagefield in a Django fixture so that the file itself is available too?
I'm afraid the short answer is that you can't do this using the FileField or ImageField classes; they just store a file path and have no real concept of the file's actual data. The long answer, however, is that anything is possible if you leverage the Django API for writing your own custom model fields.
At a minimum, you'll want to implement the value_to_string method to convert the data for serialization (there's an example in the django docs at the link above). Note that the examples at the URL link above also include mention of subclassing FileField and ImageField, which is helpful for your situation!
You'll also have to decide if the data should therefore be stored in the database, or on the file system. If the former, you will have to implement your custom class as a Blob field, including customization for every DB you wish to support; you'll also have to provide some support for how the data should be returned to the user out of the database when the HTML requests a .gif/.jpg/.png/.whatever url. If the latter, which is the smarter way to go IMHO, you'll have to implement methods for serializing, de-serializing binary data to the filesystem. Either way, if you implement these as subclasses of FileField and ImageField, you should still be able to use the Admin tools and other modules that expect such django features.
If and only if you elect to use the more involved blob approach, here's a snippet of code from an old project of mind (back when I was learning Django) that handles blob for MySQL and PostgreSQL; you'll probably be able to find a number of improvements as I haven't touched it since :-) It does not handle serialization, though, so you'll have to add that using the method above.
from django.db import models
from django.conf import settings
class BlobValueWrapper(object):
"""Wrap the blob value so that we can override the unicode method.
After the query succeeds, Django attempts to record the last query
executed, and at that point it attempts to force the query string
to unicode. This does not work for binary data and generates an
uncaught exception.
"""
def __init__(self, val):
self.val = val
def __str__(self):
return 'blobdata'
def __unicode__(self):
return u'blobdata'
class BlobField(models.Field):
"""A field for persisting binary data in databases that we support."""
__metaclass__ = models.SubfieldBase
def db_type(self):
if settings.DATABASE_ENGINE == 'mysql':
return 'LONGBLOB'
elif settings.DATABASE_ENGINE == 'postgresql_psycopg2':
return 'bytea'
else:
raise NotImplementedError
def to_python(self, value):
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
if value is None:
return value
return str(value)
else:
return value
def get_db_prep_save(self, value):
if value is None:
return None
if settings.DATABASE_ENGINE =='postgresql_psycopg2':
return psycopg2.Binary(value)
else:
return BlobValueWrapper(value)
There's no way to "include" the files in the serialized fixture. If creating a test fixture, you just need to do it yourself; make sure that some test files actually exist in locations referenced by the FileField/ImageField values. The values of those fields are paths relative to MEDIA_ROOT: if you need to, you can set MEDIA_ROOT in your test setUp() method in a custom test_settings.py to ensure that your test files are found wherever you put them.
EDIT: If you want to do it in your setUp() method, you can also monkeypatch default_storage directly:
from django.core.files.storage import default_storage
class MyTest(TestCase):
def setUp(self):
self._old_default_storage_location = default_storage.location
default_storage.location = '/some/other/place'
def tearDown(self):
default_storage.location = self._old_default_storage_location
That seems to work. default_storage is a documented public API, so this should be reliable.