Getting username in ImageField upload_to path - django

I would like to include the username in my upload_to directory path for when a user uploads an image. Here is what I currently have --
#model
class Avatar(models.Model):
avatar = models.ImageField(upload_to='images/%s' %(USERNAME) )
user = models.ForeignKey(UserProfile)
#form
class ProfilePictureForm(ModelForm):
class Meta:
model = Avatar
fields = ('avatar',)
How would I get the USERNAME in the model to be able to set the upload_to path?

upload_to can be a callable instead of a string, in which case it will be passed the current instance and the filename -- see the documentation. Something like this should work (instance.user.user because instance.user is the UserProfile, so instance.user.user is the User).
def upload_to(instance, filename):
return 'images/%s/%s' % (instance.user.user.username, filename)
class Avatar(models.Model):
avatar = models.ImageField(upload_to=upload_to)
user = models.ForeignKey(UserProfile)

Ismail Badawi answer is completely correct. Also you can use new string formatting and lambda function.
New string formatting:
def upload_to(instance, filename):
return 'images/{username}/{filename}'.format(
username=instance.user.user.username, filename=filename)
class Avatar(models.Model):
avatar = models.ImageField(upload_to=upload_to)
user = models.ForeignKey(UserProfile)
New String formatting and lambda function:
path = lambda instance, filename: 'images/{username}/{filename}'.format(
username=instance.user.user.username, filename=filename)
class Avatar(models.Model):
avatar = models.ImageField(upload_to=path)
user = models.ForeignKey(UserProfile)

Related

Django annotate with other model attributes based on a common field

I have a User model,
class User(AbstractBaseUser):
id = models.IntegerField(primary_key=True)
email = models.EmailField(unique=True)
I have another model named Company. The Company model has a reference to User model via an Integer field.
class Company(models.Model):
user_id = models.IntegerField(db_index=True)
name = models.CharField(max_length=100)
size = models.IntegerField(default=1)
I wanted to extract the company information along with user information.
basically I want a user object dictionary like this {'id':1, 'email':'abc#gmail.com','name':'foobar.co','size':400}
I want to annotate the user objects with name and size. As of now, I tried in the serializer's to_representation method. However, for millions of users this is super slow.
class UserSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField(read_only=True)
def to_representation(self, instance):
response = super(UserSerializer, self).to_representation(instance)
company = Company.objects.filter(user_id=instance.id)
if company.exists():
company = company.first()
response['name'] = company.name
response['size'] = company.size
return response
How can I achieve this annotation in the query itself.
If the links in the comment do not help you, You can use SerializerMethodField for name, and size
class UserSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField(read_only=True)
name = serializers.SerializerMethodField()
size = serializers.SerializerMethodField()
def get_name(self, obj):
# get name from DB using the User object(obj)
return name
def get_size(self, obj):
# get size from DB using the User object(obj)
return size

Django dynamic folder name based on id/username when uploading image

Good day SO.
I want to make my folder name(s) to be renamed based on the ID/username of the uploader.
My setup is as follows:
class Account(AbstractBaseUser):
username= models.CharField(...) # As per our setup, user cant enter their own username but will be auto created based on a separate py file on my view during registration
...
class Type01Account(models.Model):
account = models.OneToOneField(Account, on_delete=models.CASCADE)
...
imgPath = ""
images= models.ImageField(upload_to=imgPath, null=False, blank=False)
How should I assign imgPath? Should I assign it on models or on view? If so, how should I save it?
The upload_to argument can be a function which takes the model instance and filename and returns the path to upload:
def img_path(instance, filename):
return f"{instance.account.username}/{filename}"
class Type01Account(models.Model):
account = models.OneToOneField(Account, on_delete=models.CASCADE)
...
imgPath = ""
images= models.ImageField(upload_to=img_path, null=False, blank=False)
Reference: FileField.upload_to
A class to handle the all upload_to for any field that contains a file
import os
from django.utils import timezone
class UploadTo:
def __init__(self, name):
self.name = name
def __call__(self, instance, filename):
base_filename, file_extension = self.generate_name(filename)
path = f'files/{instance.__class__.__name__}/{self.name}/{timezone.now().strftime("%y-%m-%d")}/{base_filename}{file_extension}'
# any custom action on path
return path
def generate_name(self, filename):
base_filename, file_extension = os.path.splitext(filename)
# any custom action on file name
return base_filename, file_extension
def deconstruct(self):
# you can add some static value from model like `self.name`
return 'path.to.this.UploadTo', [self.name], {}
# Usage example
class ExampleModel(models.Model):
# add here some static value to class `news_image`
news_image = models.ImageField(_("news image"), upload_to=UploadTo('news_image'), blank=True)

(Django) How to assign the username of current user to some other model in a view?

I want to assign the username field of Passengers model to the username of the current logged in user. I cant seem to do it. Below is the code.
Models.py
class UserProfileInfo(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
profile_pic = models.ImageField(upload_to='profile_pics',blank=True)
def __str__(self):
return self.user.username
class Passengers(models.Model):
username = models.CharField(max_length = 100)
passenger_firstname = models.CharField(max_length = 100)
passenger_lastname = models.CharField(max_length = 100)
passenger_age = models.CharField(max_length = 100)
passenger_gender = models.CharField(max_length=6, choices=GENDER_CHOICES, default='female')
froms.py
class PassengerForm(forms.ModelForm):
class Meta():
model = Passengers
fields = (
'passenger_firstname',
'passenger_lastname',
'passenger_age',
'passenger_gender')
views.py
def passenger_info(request):
if request.method == "POST":
passenger_details = PassengerForm(data=request.POST)
if passenger_details.is_valid():
passenger_details.username = request.user
passenger_details.save()
else:
passenger_details = PassengerForm()
return render(request,'passenger_info.html',{'passenger_details':passenger_details})
No error message is printed out but it leaves the username field blank like below:
django-admin model object
While the answer by #Navid2zp is correct, the other issue you're having is with this line:
passenger_details.username = request.user
passenger_details is a ModelForm, and after initialisation it doesn't have an attribute username. It has a field username that can be accessed as bound field (passenger_details['username']) if the form has data or as a form field (passenger_details.fields['username']).
By giving it the attribute username you're achieving nothing since that won't be associated in any way with the field username.
But the easiest way to assign additional parameters to the instance of the form before it is saved is the following:
profile = passenger_details.save(commit=False) # gives you the instance
profile.user = request.user # assuming you're using `ForeignKey` here, calling it `user` instead of `username` for clarity
profile.save()
First of all it doesn't make any sense to do it like this. You should create a ForeignKey to user model.
Also request.user will return the user object not the username so if you need the username you should call it: request.user.username
And to fix your problem:
class Passengers(models.Model):
username = models.ForeignKey(User, null=False, blank=False)
passenger_firstname = models.CharField(max_length = 100)
passenger_lastname = models.CharField(max_length = 100)
passenger_age = models.CharField(max_length = 100)
passenger_gender = models.CharField(max_length=6, choices=GENDER_CHOICES, default='female')
also you can get the first name and the last name from User model too. also gender and age should be in UserProfileInfo model which makes more sense and Passengers model should be about linking users to bus, airplane or etc not to hold data that are not going to change.

Extending Django default user

I'm trying to build a user profile which builds on the default Django user. I'm trying to extend the default user with an extra fields like this:
class MyMember(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL) #not sure about this
birthday = models.DateField()
USERNAME_FIELD = 'user' #how to use username field from default user?
REQUIRED_FIELDS = ['birthday',]
def __unicode__(self):
return self.username
But I'm getting this error: 'Manager' object has no attribute 'get_by_natural_key'? Why?
I would also like the USERNAME_FIELD to be the username from the default user. How?
Here's the UserProfile model class I use to extend the User model in my demo website:
#Private function required by image field.
def _get_upload_file_name_w_sub_dir(instance, filename):
return "uploaded_files/{0}{1}_{2}".format("profile_pic/", (str(time()).replace(".", "_"), filename))
class UserProfile(models.Model):
# This line is required. Links UserProfile to a User model instance.
# related_name is so you can reference *this* model as "user.profile"
# instead of "user.userprofile"
user = models.OneToOneField(User, related_name="profile")
# The additional attributes we wish to include.
year_discovered = models.IntegerField(blank=True,
verbose_name="Year you discovered Billy Joel's music/became a fan")
profile_picture = models.ImageField(upload_to=get_upload_profile_pic_file_name,
blank=True, null=True)
# Override the __unicode__() method to return out something meaningful!
def __unicode__(self):
return self.user.username
Here is the official documentation on extending the user model.
This is how you would extend the default Django User model. You would want to use a ForeignKey, and then you can use dot notation to access the fields of the User model.
Here:
from django.contrib.auth.models import User
class MyMember(models.Model):
user = models.ForeignKey(User)
birthday = models.DateField()
def __unicode__(self):
# the default "username" field in the django user model
return self.user.username

While saving a Django model instance, in what order are my clean() and save() overrides applied relative to methods used as ModelField attributes?

I have a model with a first_name and last_name field, and these are used to create a filename on an ImageField. The argument for upload_to on the ImageField is this method that generates the filename with this instance's information.
When this model instance is saved, would the calls to .strip() in the clean() be applied to the fields before they are used to generate the filename? Or would I need to do .strip() on the data when it's used, as well as in the clean?
models.py:
def set_path(instance, filename):
"""
Set the path to be used for images uploaded (trainer photos).
"""
return u'about/%(first)s_%(last)s.%(ext)s' % {
'first': instance.first_name.strip(' \t').lower(), #.strip() required?
'last': instance.last_name.strip(' \t').lower(), #.strip() required?
'ext': filename.split('.')[-1]
}
class Trainer(models.Model):
"""
Trainers and their associated information.
"""
first_name = models.CharField(max_length=25)
last_name = models.CharField(max_length=25)
image = models.ImageField(upload_to=set_path, blank=True, null=True,
verbose_name="Trainer image")
description = models.TextField()
class Meta:
unique_together = ('first_name', 'last_name',)
def clean(self):
super(Trainer, self).clean()
# Are these calls to .strip() applied before the fields
# get used as `instance` to determine a filename?
self.first_name = self.first_name.strip(' \t')
self.last_name = self.last_name.strip(' \t')
self.description = self.description.strip(' \t\r\n')
If there is a callable for the upload_to argument, it is called during the save() method of the model base. The save() is of course called after clean(), so you do NOT need to strip() any fields if you've already done so in the clean() method.
You can see where the code is called on line 90 of the Django source code: https://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py
generate_filename is the stored variable that points to whatever you passed into upload_to.
So, the order is form submit -> model.full_clean() -> overridden clean() -> save(), which calls upload_to()