I made a function that generates avatars for users when they create a profile:
users/models.py
def random_image():
directory = os.path.join(settings.BASE_DIR, 'media')
files = os.listdir(directory)
images = [file for file in files if os.path.isfile(os.path.join(directory, file))]
rand = choice(images)
return rand
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
avatar_new = models.ImageField (default=random_image)
def __str__(self):
return f'{self.user.username} Profile'
For changing avatar i am using django-avatar
There is a line like this (settings):
DEFAULT_URL = ("avatar/img/default.jpg")
I would like to display not a static image (default.jpg), but the result of the random function, which assigned the user an avatar. How can I make sure that the default url contains not a static image, but the result of the field avatar_new?
Tried itDEFAULT_URL = Profile.avatar_new error. Need help pls
django-avatar has its own model for storing Avatars. You can try to add your own AVATAR_PROVIDER as they describe in their documentation.
Something like this might work:-
from avatar.models import Avatar
from django.core.files import File
class RandomAvatarProvider(object):
#classmethod
def get_avatar_url(cls, user, size):
if not user or not user.is_authenticated:
return
avatar = Avatar(user=user, primary=True)
random_image = cls.random_image()
image_file = File(open(random_image, 'rb'))
avatar.avatar.save(image_file.name, image_file)
avatar.save()
return avatar.avatar_url(size)
#staticmethod
def random_image():
directory = os.path.join(settings.BASE_DIR, 'media')
files = os.listdir(directory)
images = [file for file in files if os.path.isfile(os.path.join(directory, file))]
rand = choice(images)
return os.path.join(directory, rand)
Also you would have to add this into your settings:-
AVATAR_PROVIDERS = (
'avatar.providers.PrimaryAvatarProvider',
'avatar.providers.GravatarAvatarProvider',
'app_name.where_provider_is.RandomAvatarProvider',
)
The random avatar provider must be last while you can chose which ones you want to keep above in which order.
Given your usecase perhaps you might want to remove GravatarAvatarProvider from AVATAR_PROVIDERS as it would always return some url.
Edit: add 'avatar.providers.DefaultAvatarProvider' at the end of AVATAR_PROVIDERS and a AVATAR_DEFAULT_URL for unauthenticated users, or write some logic for them in the space where I am returning nothing.
Related
I am developing a django application where the user chooses a machine learning model from a drop down list and uploads an image for classification. This image was initially saved in the project directory (bad, I know) so that I can use it in the classification.
Now I save these images in Azure Storage, but at the moment I can't find a way to access them without having to save them locally to classify them, so I think I'll have to temporarily save them in the project directory and once I use them in the ml model, then I remove the images.
I would like to deploy this application to Azure web service, so I consider it is a bad idea to save and delete images in the project directory.
You can see app's form here
models.py
image is saved in Azure Storage, the other fields in Azure Database for PostgreSQL.
class UploadedImage(models.Model):
image = models.ImageField(upload_to='%Y/%m/%d/')
uploaded = models.DateTimeField(auto_now_add=False, auto_now=True)
title = models.CharField(max_length=50)
prediction = models.FloatField(null=True, blank=True)
def __str__(self):
return self.title
forms.py
class UploadImageForm(forms.ModelForm):
EXTRA_CHOICES = [
('MOB', 'MobileNetV2'),
('VGG', 'VGG-19'),
('CNN', 'CNN 3BI'),
]
predicted_with = forms.ChoiceField(label="Modelo Predictivo",
choices=EXTRA_CHOICES, required=True,
widget=forms.Select(attrs={'class': 'form-control'})
)
class Meta:
model = UploadedImage
fields = [
'image',
]
widgets = {
'image': forms.FileInput(attrs={'class':'custom-file-input'}),
}
views.py
def make_prediction(image_to_predict, model='MOB'):
tf.keras.backend.reset_uids()
folders = {'VGG': 'vgg', 'MOB': 'mobilenet', 'CNN': 'cnn3', 'MSG': 'mobile_sin_gpu'}
model_as_json = 'upload_images/model/%s/modelo.json' % (folders[model])
weights = 'upload_images/model/%s/modelo.h5' % (folders[model])
json_file = open(model_as_json, 'r')
loaded_json_model = json_file.read()
json_file.close()
model = tf.keras.models.model_from_json(loaded_json_model)
model.load_weights(weights)
image = [image_to_predict]
data = img_preprocessing.create_data_batches(image)
return model.predict(data)
def upload_image_view(request):
if request.method == 'POST':
form = forms.UploadImageForm(request.POST, request.FILES)
if form.is_valid():
m = form.save(commit=False)
try:
pred = make_prediction(m.image.path, form.cleaned_data['predicted_with'])[0][0]
if pred > 0.5:
# Code continue...
if status == 200:
m.prediction = pred
m.title = m.image.path
m.save()
# Code continue...
The above snippet worked when I initially saved the images in the project directory but when I started saving the images in Azure Storage I started getting this error:
This backend doesn't support absolute paths.
So I changed the following line: pred = make_prediction(m.image.name, form.cleaned_data['predicted_with'])[0][0]
However now I have this error: NewRandomAccessFile failed to Create/Open: image-100.png : The system cannot find the file specified. ; No such file or directory [[{{node ReadFile}}]] [[IteratorGetNext]] [Op:__inference_predict_function_845] Function call stack: predict_function
For this reason I think my solution would be to temporarily save the image in the project directory, use it in the model and then delete it, however, I do not think it is ideal.
What approach is appropriate to follow in this case?
Title is a bit confusing, but basically I have an s3 path stored as a string
class S3Stuff(Model):
s3_path = CharField(max_length=255, blank=True, null=True)
# rest is not important
There are existing methods to download the content given the url, so I want to utilize that
def download_from_s3(bucket, file_name):
s3_client = boto3.client(bleh_bleh)
s3_response = s3_client.get_object(Bucket=s3_bucket, Key=file_name)
return {'response': 200, 'body': s3_response['Body'].read()}
s3_path can be broken into bucket and file_name. This works very easily when I use my own frontend because I can do whatever I want with it, but I don't know how to apply this to admin
class S3StuffAdmin(admin.StackedInline):
model = S3Stuff
fields = ('s3_path', )
Now how do I call that method and make the display a link that says "download"
I don't think this function will be much useful for generating download links, instead use the boto3's presigned_url like this:
from django.utils.html import format_html
class S3StuffAdmin(admin.StackedInline):
model = S3Stuff
fields = ('s3_path', )
readonly_field = ('download',)
def download(self, obj):
s3_client = boto3.client(bleh_bleh)
url = s3_client.generate_presigned_url('get_object', Params = {'Bucket': 'bucket', 'Key': obj.s3_path}, ExpiresIn = 100)
return format_html('<a href={}>download</a>'.format(url))
I have two models,Foto and FotoMetadata. Foto just has one property called upload, that is an upload field. FotoMetadata has a few properties and should receive metadata from the foto uploaded at Foto. This can be done manually at the admin interface, but I want to do it automatically, i.e: when a photo is uploaded through admin interface, the FotoMetadata is automatically filled.
In my model.py I have a few classes, including Foto and FotoMetadata:
class Foto(models.Model):
upload = models.FileField(upload_to="fotos")
def __str__(self):
return '%s' %(self.upload)
class FotoMetadata(models.Model):
image_formats = (
('RAW', 'RAW'),
('JPG', 'JPG'),
)
date = models.DateTimeField()
camera = models.ForeignKey(Camera, on_delete=models.PROTECT)
format = models.CharField(max_length=8, choices=image_formats)
exposure = models.CharField(max_length=8)
fnumber = models.CharField(max_length=8)
iso = models.IntegerField()
foto = models.OneToOneField(
Foto,
on_delete=models.CASCADE,
primary_key=True,
)
When I login at the admin site, I have an upload form related to the Foto, and this is working fine. My problem is that I can't insert metadata at FotoMetadata on the go. I made a function that parse the photo and give me a dictionary with the info I need. This function is called GetExif is at a file called getexif.py. This will be a simplified version of it:
def GetExif(foto):
# Open image file for reading (binary mode)
f = open(foto, 'rb')
# Parse file
...
<parsing code>
...
f.close()
#create dictionary to receive data
meta={}
meta['date'] = str(tags['EXIF DateTimeOriginal'].values)
meta['fnumber'] = str(tags['EXIF FNumber'])
meta['exposure'] = str(tags['EXIF ExposureTime'])
meta['iso'] = str(tags['EXIF ISOSpeedRatings'])
meta['camera'] =str( tags['Image Model'].values)
return meta
So, basically, what I'm trying to do is use this function at admin.py to automatically populate the FotoMetadata when uploading a photo at Foto, but I really couldn't figure out how to make it. Does any one have a clue?
Edit 24/03/2016
Ok, after a lot more failures, I'm trying to use save_model in admin.py:
from django.contrib import admin
from .models import Autor, Camera, Lente, Foto, FotoMetadata
from fotomanager.local.getexif import GetExif
admin.site.register(Autor)
admin.site.register(Camera)
admin.site.register(Lente)
admin.site.register(FotoMetadata)
class FotoAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# populate the model
obj.save()
# get metadata
metadados = GetExif(obj.upload.url)
# Create instance of FotoMetadata
fotometa = FotoMetadata()
# FotoMetadata.id = Foto.id
fotometa.foto = obj.pk
# save exposure
fotometa.exposure = metadados['exposure']
admin.site.register(Foto, FotoAdmin)
I thought it would work, or that I will have problems saving data to the model, but actually I got stucked before this. I got this error:
Exception Type: FileNotFoundError
Exception Value:
[Errno 2] No such file or directory: 'http://127.0.0.1:8000/media/fotos/IMG_8628.CR2'
Exception Location: /home/ricardo/Desenvolvimento/fotosite/fotomanager/local/getexif.py in GetExif, line 24
My GetExif function can't read the file, however, the file path is right! If I copy and paste it to my browser, it downloads the file. I'm trying to figure out a way to correct the address, or to pass the internal path, or to pass the real file to the function instead of its path. I'm also thinking about a diferent way to access the file at GetExif() function too. Any idea of how to solve it?
Solution
I solved the problem above! By reading the FileField source, I've found a property called path, which solve the problem. I also made a few other modifications and the code is working. The class FotoAdmin, at admin.py is like this now:
class FotoAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# populate the model
obj.save()
# get metadata
metadados = GetExif(obj.upload.path)
# Create instance of FotoMetadata
fotometa = FotoMetadata()
# FotoMetadata.id = Foto.id
fotometa.foto = obj
# set and save exposure
fotometa.exposure = metadados['exposure']
fotometa.save()
I also had to set null=True at some properties in models.py and everything is working as it should.
I guess you want to enable post_save a signal
read : django signals
Activate the post_save signal - so after you save a FOTO you have a hook to do other stuff, in your case parse photometa and create a FotoMetadata instance.
More, if you want to save the foto only if fotometa succeed , or any other condition you may use , pre_save signal and save the foto only after meta foto was saved.
I am building a Django project where users can upload pictures. I am wondering what I should do to not show the original picture name.
I want the url to be something like /pic/randomnumber, and when the picture is downloaded from the website, it would have the name randomnumber.jpg. For example, all the pictures on Tumblr have the name tumblr_blabla.jpg.
I think this is something that should be done in models.py, but I am not quite sure how to implement it.
IMO you should write method save in your model
Something like that:
from PIL import Image
import os
class YOURS_MODEL_NAME(models.Model):
photo = models.ImageField(upload_to="photos")
def save(self, miniature=True):
super(YOURS_MODEL_NAME, self).save()
if miniature:
filepath = self.photo.path
image = Image.open(filepath)
new_filepath = filepath.split('.')
new_filepath = '.'.join("HERE YOU CAN ADD EVERYTHING TO PATH TO THIS PHOTO") + "." + new_filepath[-1].lower()
try:
image.save(new_filepath, quality=90, optimize=1)
except:
image.save(new_filepath, quality=90)
photo_name = self.photo.name.split('.')
photo_name = '.'.join("HERE YOU CAN ADD EVERYTHING YOU WANT TO 'PHOTO NAME'") + "." + photo_name[-1].lower()
self.photo = photo_name
self.save(miniature=False)
# remove old image
os.remove(filepath)
The upload_to argument in your Model definition can be a callable function which you use to customize the name of the file. Taken from the Django docs on
FileField (of which ImageField is a subclass):
upload_to takes two arguments: instance and filename, (where filename is the original filename, which you may also chose to ignore).
Something similar to this in models.py should do the trick:
def random_filename(instance, filename):
file_name = "random_string" # use your choice for generating a random string!
return file_name
class SomeModel(models.Model):
file = models.ImageField(upload_to=random_filename)
(this is similar to the answer this question about FileFields).
If you are going down this path, I would recommend that you use either the hash/checksum or date/time of the file upload. Something along these lines should work (although I haven't tested it myself!):
from hashlib import sha1
def unique_filename(instance, field):
filehash = sha1()
for chunk in getattr(instance, field).chunks():
filehash.update(chunk)
return filehash
class SomeModel(models.Model):
file = models.ImageField(upload_to=unique_filename(field='file'))
Hope this helps!
I want to upload many images from URLs while I create objects with a script.
#models.py
class Widget(TimeStampedModel):
name = CharField ... etc, etc
pic = ThumbnailerImageField(_('Widget Pic'),
upload_to='widget/pic/',
help_text = _('Please submit your picture here.'),
null=True, blank=True)
so I thought of using the save method in that class to download and save the images. So my script creates the Widget objects and saves the image url, and then the save method tries to download and save the image. My save method so far is:
def save(self, *args, **kwargs):
if self.pic:
if self.pic.name.startswith( 'http://') and self.pic.name.endswith(('.png', '.gif', '.jpg', '.jpeg', '.svg')):
my_temp_pic = open('test.image', 'w')
my_temp_pic.write(urllib2.urlopen(self.pic.name).read())
my_temp_pic.close()
my_temp_pic = open('test.image')
thumbnailer = get_thumbnailer(my_temp_pic, relative_name = self.slug+'.'+self.pic.name.split('.')[-1])
self.pic = thumbnailer.get_thumbnail({'size': (200, 0), 'crop': False})
super(Widget, self).save(*args, **kwargs)
I've tried to open the file in different ways with .read() or .open() ... but the only way I found (above) feels quite hackish (save some temp file with the image, re-open, then save). Is there a better way? I'm I missing a straightforward way to do this?
Save the temporary file is the only solution I know too. Check this: http://djangosnippets.org/snippets/1890/
So basically you don't need to do hackish like close() and open() again. You can do:
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
# ... your code here ...
my_temp_pic = NamedTemporaryFile(delete=True)
my_temp_pic.write(urllib2.urlopen(self.pic.name).read())
my_temp_pic.flush()
relative_name = '%s.%s' % (self.slug, self.pic.name.split('.')[-1])
thumbnailer = get_thumbnailer(my_temp_pic, relative_name=relative_name)
# ... your code again ...
Hope it helps.