I've implemented a custom model field in Django. It is an image field that allows assiging an URL string to load an image from, in addition to directly assigning a file.
import uuid
import urllib.request
from django.core.files.base import ContentFile
from django.db import models
from django.db.models.fields.files import ImageFileDescriptor
class UrlImageFileDescriptor(ImageFileDescriptor):
def __set__(self, instance, value):
# If a string is used for assignment, it is used as URL
# to fetch an image from and store it on the server.
if isinstance(value, str):
try:
response = urllib.request.urlopen(value)
image = response.read()
name = str(uuid.uuid4()) + '.png'
value = ContentFile(image, name)
except:
print('Error fetching', value)
pass
super().__set__(instance, value)
class UrlImageField(models.ImageField):
descriptor_class = UrlImageFileDescriptor
In general, the field works. But for some reason, Django itself internally assigns string values to it. Every time a query set of models using the field gets filtered, __set__
is called with a string so that the print statement in the except clause fires Error fetching upload/to/50e170bf-61b6-4670-90d1-0369a8f9bdb4.png
.
I could narrow down the call to django/db/models/query.py
from Django 1.7c1.
def get(self, *args, **kwargs):
"""
Performs the query and returns a single object matching the given
keyword arguments.
"""
clone = self.filter(*args, **kwargs)
if self.query.can_filter():
clone = clone.order_by()
clone = clone[:MAX_GET_RESULTS + 1]
num = len(clone) # This line causes setting of my field
if num == 1:
return clone._result_cache[0]
# ...
Why is the line causing my field's __set__
to get executed? I could validate the input value to be a valid URL to work around this, but I'd like to know the reason first.