Unique filename of uploaded file using the django FORM - python

I'm trying to generate a unique filename for the uploaded file using the Django forms. I've tried uuid_upload_path app but that app doesn't work with the form. Below is my code
Forms.py
class HelpGuideForm(forms.ModelForm):
title = forms.CharField(max_length = 50)
image = forms.ImageField(required = False)
class Meta:
model = Helpguide
fields = ['title', 'image']
Models.py
from uuid_upload_path import upload_to
class HelpguideImage(models.Model):
image = models.ImageField(upload_to = upload_to, blank=True, null=True)
I want a unique name for all uploaded files. something like sd564sadasd61.jpg. I'm using Django 2.2

In your Model you can set the upload_to of the imagefield to a function and then generate the uuid.
A very simple (untested) example:
import uuid
Class MyModel(models.Model):
def get_path(instance, filename):
extension = filename.split('.')[-1]
uuid = uuid.uuid1().hex
return f'path/to/file/{uuid}.{extension}'
image = ImageField(upload_to=get_path)

What I understand of your problem, you can set initial for FORM class when initialising it. like:
help_guide_form = HelpGuideForm(initial={'headline': uuid.uuid4().hex}, instance= Helpguide)
from django docs. Also see the initial reference.

Related

Incorrect path to save the file in Django

I am now getting the file from my front-end and I set my model like this.
model.py
class User(models.Model):
name = models.CharField(max_length=50)
image= models.FileField(upload_to='image/', default=None)
intro= models.FileField(upload_to='info/', default=None)
view.py
class UserViewSet(viewsets.ModelViewSet):
serializer_class = LsRequestSerializer
queryset = User.objects.all()
http_method_names = ['post']
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
field = '__all__'
def create(self, validated_data):
newUser = User.objects.create(
name = validated_data['name']
image = validated_date['image']
intro = validated_date['intro']
)
return newUser
However, when I did the HTTP.POST in Postman, it gave the wrong path to save the image.
"http://localhost:8000/media/Koala.jpg"
But based on my design in the model with path_to, it should give:
"http://localhost:8000/media/image/Koala.jpg"
Update:
The strange thing is that when I tried to update a user by giving a new image using HTTP.Patch method, it then has the right path.
Update:
It turns out to be the problem that I cannot have multiple input filefield and upload_to different subfolders. How to solve this problem?
If I put the serializer as above, it can find the right path but it also means that these two fields are required. But in fact, these two fields are optional.
But if I put the if statement outside the create function, it cannot detect the correct path.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
field = 'all'
def create(self, validated_data):
newUser = User.objects.create(
name = validated_data['name']
image = None
intro = None
)
if validate_data['image']:
newUser.image = validate_data['image']
if validate_data['intro']:
newUser.image = validate_data['intro']
return newUser
What's wrong with my code?
Well you should first check that if you specified the right path for media in your setting.py it should be something like this:
The place that you want to store those pictures:
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
And as you want to see them like "http://localhost:8000/media/whatever/whatever.jpg" you should specified the MEDIA_URL like this:
MEDIA_URL = '/media/'
And then as you did it in your models the argument will be like:
upload_to='image/'
You can also read more about it here!
And as a side note if you want to deal with pictures then you can use ImageField rather than FileField.

Difference between NestedStackedInline and NestedTabularInline

I using a nested model in a Django project.
The following snippet code is models.py:
from django.db import models
from django.db.models.deletion import CASCADE
class Model_(models.Model):
name = models.CharField(max_length=50, default="This is a model")
frequently = models.FloatField(default=1.0)
def __str__(self):
return self.name
class SubModel(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=8, default='0x')
model_ = models.ForeignKey(Model_, on_delete=CASCADE)
def __str__(self):
return self.name
class Metadata(models.Model):
key = models.CharField(max_length=100)
value = models.CharField(max_length=100)
sub_model = models.ForeignKey(SubModel, on_delete=CASCADE)
This is my admin.py script:
from django.contrib import admin
from nested_inline.admin import NestedTabularInline, NestedStackedInline,\
NestedModelAdmin
from <djano-application-name>.models import Model_, SubModel, Metadata
class MetadataAdmin(NestedTabularInline):
model = Metadata
extra = 1
class SubModelAdmin(NestedStackedInline):
model = SubModel
inlines = [MetadataAdmin]
extra = 1
class Model_Admin(NestedModelAdmin):
model = Model_
inlines = [SubModelAdmin]
list_display = ['name']
admin.site.register(Model_, Model_Admin)
Question:
What is the difference between NestedStackedInline and NestedTabularInline in admin.py script?
[NOTE]:
Versions: Python 2.7 and Django 1.11
If you are using django-nested-inline, It means you wanted to edit models on the same page as a parent model and add more than 1 level of children at once with the parent object in admin.
The Django admin is just a normal Django application and you can't have a second level of inlines(nested forms) in the default Django admin.
The difference between NestedStackedInline and NestedTabularInline is just Layout. Indeed, both work exactly the same behind the scenes, the only difference is the template used for rendering. Check the official docs. So, picking one for your project is only a matter of preference regarding the interface layout.
This is how NestedStackedInline will look, each field of the model is under other.
and this is NestedTabularInline, each field of the model is in one line, column wise

django instance.id=None when uploading image

instance.id is returning None when upload images through the admin page. The idea was to upload all the images of each Residence to a different folder. Here's my code:
models.py
from django.db import models
import os
def get_image_path(instance, filename):
return os.path.join('photos', "residence_%s" % instance.id, filename)
# Create your models here.
class Residence(models.Model):
big_image = models.ImageField("Main Image",upload_to=get_image_path)
small_images = models.ImageField("Small Images",upload_to=get_image_path, blank=True, null=True)
settings.py
MEDIA_URL = '/media/'
EDIT: It works if I modify the image after the model is already added.
You can't do it in that way unless you implement your custom dynamic file upload field. Because you try to access instance.id, but instance isn't saved yet and doesn't have an id.
Here you are some resources that will help you achieve what you want:
dynamic file upload path with current instance id
Django admin file upload with current model id
Another nice way to solve this problem that requires much less code is to have your model use a UUID for the primary key rather then the database generated id. This means at the point the model is saved for the first time the UUID is already known and can be used with any upload_to callbacks.
So for the original example you would do something like this
from django.db import models
import uuid
import os
def get_image_path(instance, filename):
return os.path.join('photos', "residence_%s" % str(instance.id), filename)
# Create your models here.
class Residence(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
big_image = models.ImageField("Main Image",upload_to=get_image_path)
small_images = models.ImageField("Small Images",upload_to=get_image_path, blank=True, null=True)
See Django's UUIDField reference for more info
If you don't specify an update_to on the ImageField you could upload to your media root then change the path using a post_save signal.
#receiver(post_save, sender=Product)
def update_file_path(instance, created, **kwargs):
if created:
initial_path = instance.image.path
new_path = settings.MEDIA_ROOT + f'/product_{instance.id}/{instance.image.name}'
os.makedirs(os.path.dirname(new_path), exist_ok=True)
os.rename(initial_path, new_path)
instance.image = new_path
instance.save()
you can create a model instance by passing cleaned data from your form as **kwargs to django model i did it that way & its much easier than anything else
in your views post method add this (this code is from my project not adapted to this question)
pk = request.session['_auth_user_id']
user_obj = User.objects.get(pk=pk)
lab_form_instance = lab_form(request.POST,request.FILES)
lab_form_instance.save(commit=False)
# here you can put the form.is_valid() statement
lab_form_instance.cleaned_data['owner'] =user_obj # here iam adding additional needed data for the model
obj = lab(**lab_form_instance.cleaned_data)
obj.save()

Django - Accessing attributes through a OneToOneField

I am very new to Django and I was wondering if I could request some help with an issue I am facing. I'm trying to build a set of models in Django that is structured as follows:
An app_user describes a user of the application I am building.
An app_job describes a job that the user wants to run on the app, with several associated inputs (upload1, upload2, upload3). A user can run many jobs; hence, I use the many-to-one (ForeignKey) relationship between job and user. When an app_job is created, I want its associated files to be uploaded to a directory determined by the associated user's username and num_jobs attribute, as shown.
When I run python manage.py makemigrations, I receive the following error: AttributeError: 'ForeignKey' object has no attribute 'user'. Which begs the question, how can I access the underlying app_user's information from the app_job class?
Thanks for the help.
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.forms import ModelForm
class app_user(models.Model):
user = models.OneToOneField(User)
num_jobs = models.IntegerField(default = 0)
def __unicode__(self):
return self.user.get_username()
class app_job(models.Model):
app_user = models.ForeignKey(app_user)
upload1 = models.FileField(upload_to = app_user.user.get_username() + "/" + str(app_user.num_jobs) , blank = True, null = True)
upload2 = models.FileField(upload_to = app_user.user.get_username() + "/" + str(app_user.num_jobs))
upload3 = models.FileField(upload_to = app_user.user.get_username() + "/" + str(app_user.num_jobs) )
Okay there are a couple of issues here. But nothing a little education cannot fix.
models should be CamelCased this makes it easier to read and is generally good practise.
You do not need to prefix models with app_ its much cleaner and easier to read without this.
Anyway,
You app_job model ForeignKey should be to User not to the app_user model. By doing this you can still gain access to the app_user data.
You also need to modify the upload_to attributes also. Whilst uploads_to can be a string value it cannot be evaluated the way you are currently doing this. Check out the django filefield documentation for details (shameless plug, I recently re-wrote this part of the documentation).
Instead you need to do the following:
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class app_job(models.Model):
app_user = models.ForeignKey(User)
upload1 = models.FileField(upload_to=user_directory_path , blank = True, null = True)
upload2 = models.FileField(upload_to=user_directory_path
upload3 = models.FileField(upload_to=user_directory_path, blank=True, null=True)
What this is doing is upload_to calls the function user_directory_path to generate the file path.
By following the above you should have something like:
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.forms import ModelForm
class UserProfile(models.Model):
"""
In settings.py you will want to add a link to AUTH_USER_PROFILE
See https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#extending-the-existing-user-model
"""
user = models.OneToOneField(User)
num_jobs = models.IntegerField(default=0)
def __unicode__(self):
return self.user.get_username()
def upload_dir(instance, filename):
return instance.user.get_username() + "/" + str(instance.user.num_jobs)
class Job(models.Model):
user = models.ForeignKey(User)
upload1 = models.FileField(upload_to=upload_dir, blank = True, null = True)
upload2 = models.FileField(upload_to=upload_dir, blank=True, null=True)
upload3 = models.FileField(upload_to=upload_dir, blank=True, null=True)
Based on this answer to a similar question something like this should work:
# models.py
from django.db import models
from django.contrib.auth.models import User
class app_user(models.Model):
user = models.OneToOneField(User)
num_jobs = models.IntegerField(default = 0)
def __unicode__(self):
return self.user.get_username()
def content_file_name(instance, filename):
return '/'.join([instance.app_user.user.get_username(), str(instance.app_user.num_jobs), filename])
class app_job(models.Model):
app_user = models.ForeignKey(app_user)
upload1 = models.FileField(upload_to = content_file_name , blank = True, null = True)
upload2 = models.FileField(upload_to = content_file_name)
upload3 = models.FileField(upload_to = content_file_name)
As you have been told there are several issues. But its good to learn :).
First the class name should start with an upper letter and use "camelCase",
in your case, "AppUser" and "AppJob". Personally I would not use "App" as suffix instead I would just use "MyUser" and "Job".
The error you are getting "'ForeignKey' object has no attribute 'user" is because in your "app_job" model you have a ForeignKey to app_user, which is OK, that creates the relationship between the both models, but then in the FileField's you are using that "foreignkey" object which have the same name as your model "app_user" and that ForeignKey instance does not have an attribute called "user", Do you get this?
The information the guys gave you related to the "upload_to" is correct :).
you can get more info in the Django docs
Thanks

Django - Get Object with ForeignKey

versions:
Python 3.4
Django 1.7
I created a gallery app with different galleries and their own images.
my models:
from django.db import models
class Gallery(models.Model):
title = models.CharField(max_length=200)
pub_date = models.DateTimeField('publish date')
def __str__(self):
return self.title
class Image(models.Model):
title = models.CharField(max_length=200)
gallery = models.ForeignKey(Gallery)
pub_date = models.DateTimeField('publish date')
file = models.ImageField(upload_to='img/gallery/'+str(Gallery.objects.get(pk=str(gallery)).id)+'/')
def __str__(self):
return self.title
you see my Gallery and Image model. While creating an Image in the Backend it should create a folder dynamicly in "img/gallery/gallery_id"
my problem is that Image.gallery is a ForeignKey here and I cant convert it into a int or string to use it. Is there a function to get the ID from my Gallery object with the ForeignKey from my Image object?
my solution
Gallery.objects.get(pk=str(gallery)).id
is not working.
btw: I thought foreignkey is always an int like id? isnt it?
The way Django works, a convenient method is automatically added that will give you the model instance that the foreign key represents. This is your gallery field. There is another field, called the same but with _id appended, that holds the actual foreign key value.
As petkostas said, you can pass a function that accepts the instance and filename arguments. You should use gallery_id instead of gallery.id to prevent an unnecessary call to the database.
def gallery_folder(instance, filename):
return '/'.join(['img/gallery', instance.gallery_id, filename])
class Image(models.Model):
...
file = models.ImageField(upload_to=gallery_folder)
upload to can be a callback:
https://docs.djangoproject.com/en/1.6/ref/models/fields/#django.db.models.FileField.upload_to
so:
def gallery_folder(instance, filename):
return '/'.join(['img/gallery', instance.gallery.id, filename])
class Image(models.Model):
title = models.CharField(max_length=200)
gallery = models.ForeignKey(Gallery, related_name='images')
pub_date = models.DateTimeField('publish date')
file = models.ImageField(upload_to=gallery_folder)
def __str__(self):
return self.title
Also add related_name, makes reverse queries easier, also you can opt for instance.gallery.title if you want.

Categories

Resources