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

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)

Related

How do I best restrict by user and by data model using Django?

I'm using django-guardian and I encountered some issues with the default mixins. And I want to know if there's a better way to do this.
GitHub Link: https://github.com/iaggocapitanio1/django_homepage
Problem:
If I want to limit access at both the model and object levels, using these two mixins (PermissionRequiredMixin, PermissionListMixin) is not a very easy task. Because the permissions_required attribute is overridden. To get around this I had to create a new attribute "object_permission" and do the following:
Model Looks like:
# Create your models here.
from django.db import models
from localflavor.br import models as localModels
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
class Customer(models.Model):
user: User = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
class Company(models.Model):
user: User = models.OneToOneField(User, on_delete=models.CASCADE)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='comapnies')
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
class Project(models.Model):
name = models.CharField(max_length=100)
owner = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='projects')
class Meta:
permissions = (('read_project', 'Read Project'),)
def __str__(self):
return self.name
class House(models.Model):
rooms = models.IntegerField()
postal_code = localModels.BRPostalCodeField()
project = models.ForeignKey(Project, on_delete=models.CASCADE)
Here I needed to create a new attribute ("object_permission") to limit object-level access
in the View:
class ProjectsListView(PermissionRequiredMixin, PermissionListMixin, ListView):
template_name = 'home/projects.html'
model = models.Project
permission_required = ["homepage.view_project"]
object_permission = ["read_project"]
redirect_field_name = 'next'
login_url = 'login/'
get_objects_for_user_extra_kwargs = {}
def get_object_permission(self, request: HttpRequest = None) -> List[str]:
if isinstance(self.object_permission, str):
perms = [self.object_permission]
elif isinstance(self.object_permission, Iterable):
perms = [p for p in self.object_permission]
else:
raise ImproperlyConfigured("'PermissionRequiredMixin' requires "
"'permission_required' attribute to be set to "
"'<app_label>.<permission codename>' but is set to '%s' instead"
% self.permission_required)
return perms
def get_get_objects_for_user_kwargs(self, queryset):
return dict(user=self.request.user,
perms=self.get_object_permission(self.request),
klass=queryset,
**self.get_objects_for_user_extra_kwargs)
#receiver(post_save, sender=models.Project)
def project_post_save(sender, **kwargs):
"""
Create a Profile instance for all newly created User instances. We only
run on user creation to avoid having to check for existence on each call
to User.save.
"""
project: models.Project = kwargs["instance"]
created: bool = kwargs["created"]
if created:
user = models.User.objects.get(pk=project.owner.user.id)
assign_perm("read_project", user, project)
Am I using the right approach to filter data relative to each user? How do I combine both the page access limitation and the relative data of each user in a class model view?

Django has no object attribute named text - but I don't expect it to?

I have the below structure,
When I click on the model name in the admin view, I get the below error. What does this mean?
AttributeError at /admin/app/tasksx/
'tasksx' object has no attribute 'text'
Request Method: GET
Admin.py
from django.contrib import admin
from .models import tasksx
admin.site.register(tasksx)
Views.py
def create_task(request):
if request.method == 'POST':
creator = request.user
job_title = 'data engineer'
skill_name = request.POST.get('skill_name')
starting = request.POST.get('starting')
description = request.POST.get('description')
target_date = request.POST.get('target_date')
i = tasksx.objects.create(creator=creator, job_title=job_title, skill_name=skill_name, starting=starting, current=starting, description=description, target_date=target_date)
messages.success(request, ('Skill created'))
return redirect('index')
models.py
class tasksx(models.Model):
job_title = models.CharField(max_length=400, default="data")
creator = models.CharField(max_length=400, default="none")
skill_name = models.CharField(max_length=400, default="none")
starting = models.CharField(max_length=400, default="none")
current = models.CharField(max_length=400, default="none")
description = models.CharField(max_length=4000000, default="none")
target_date = models.DateTimeField(default=datetime.now)
def __str__(self):
return self.text
Expanding my comments to avoid extended discussion:
In your tasksx model's __str__ method you are trying to return self.text when you don't have a text field anywhere in the model.
If you want to display the title, modify the method's return to.
def __str__(self):
return self.job_title
Now, if you want to see all fields in the admin interface, you would need to modify your app's admin.py.
admin.py
from django.contrib import admin
from .models import tasksx
class Tasksx_Admin(admin.modelAdmin):
# Add whatever fields you want to display in the admin
# in list_diplay tuple.
list_display = ('job_title', 'creator', 'skill_name', 'starting', 'current', 'description', 'target_date', )
# Register the Taskx_Admin class.
admin.site.register(tasksx, Taskx_Admin)
In the tasksx model you defined:
def __str__(self):
return self.text
But there is no text property in the model.

Upload images based on matching field and image name

I'm building an inventory list that includes the fields listed below in my Django model. I have a folder full of images that the name of the images correspond to the the Item Number. For install, the item number may be 4119, then the image file name will be 4119.jpg.
I would like to write a script that iterated over the items in the database and uploads the corresponding image. Any thoughts on where to start are greatly appreciated.
from django.db import models
def directory_path(instance, filename):
return '{0}/{1}/{2}'.format(supplier.name, pricelist.item_number, filename)
class Supplier(models.Model):
name = models.CharField(max_length=80)
def __str__(self):
return self.name
class PriceList(models.Model):
supplier = models.ForeignKey('Supplier', on_delete=models.CASCADE)
item_number = models.CharField(max_length=80)
description = models.CharField(max_length=120)
case_quantity = models.CharField(max_length=80)
piece_list_amount = models.CharField(max_length=80)
partner_list_amount = models.CharField(max_length=80)
upload = models.ImageField(upload_to=directory_path)
def __str__(self):
return self.item_number
You can look at my code and implement exactly what you need ... I hope this will be helpful for you:
import os
from Project.settings import BASE_DIR
from apps.showcase.models import Product
from django.core.files import File
obj_id_to_img_filename_mapping = (
(175, 'full_ef58c1a5af252d4459055481b1bbfa76.jpg'),
(176, 'full_2111856a6a092a3c65639aff56070aef.jpg'),
(177, 'full_b8154e9b348561ee295c2d1651ecca3c.gif'),
# ...
)
def update_product_images():
print('Starting upload products images...\n')
for product_id, file_name in obj_id_to_img_filename_mapping:
if file_name:
product = Product.objects.get(pk=product_id)
absolute_filename = os.path.join(BASE_DIR, 'apps/showcase/static/showcase/images/initial_data/product', file_name)
local_file = open(absolute_filename, 'r')
django_file = File(local_file)
product.image.save(file_name, django_file, save=True)
print(absolute_filename + '\n')

Django - Get database alias from QuerySet

I want to set a dynamic upload_to in the ImageField fields using a method. Something like this:
def content_file_name(instance, filename):
return '/'.join(['content', instance.database_alias, filename])
class Content(models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey(User)
file = models.FileField(upload_to=content_file_name)
Update
I don't know if is this useful, but I'm using Django 1.7.1
I'm doing a test with the model above:
models.py
from django.db import models
def content_file_name(instance, filename):
print "INSTANCE(%s)" % instance._state.db
return "/".join(instance._state.db, filename)
class Cliente(models.Model):
class Meta:
verbose_name = "Cliente"
verbose_name_plural = "Clientes"
Nombre = models.CharField("Nombre", max_length=50)
Foto = models.ImageField(upload_to=content_file_name)
def __unicode__(self):
return "Hello World"
MY_FIELDS = ["Nombre", "Foto"]
I'm getting in terminal on form submit:
Terminal
INSTANCE(None)
[19/May/2015 21:19:55] "POST /default/clientes/new HTTP/1.1" 500 168242
Another option could be making an import and get the database alias, but I don't know where get this from yet.
instance._state.db should return the database alias.
In Django 2.2.14, you can access the database alias using instance.db
qryset_employee = Employees.objects.using('OrgChartDatabase')
print(qryset_employee.db) ## Prints 'OrgChartDatabase'

Getting username in ImageField upload_to path

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)