Using callable as path attribute of Django's FilePathField? - django

I have the following model that includes a file upload by a user.
def resume_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/resume/<filename>
return 'user_{0}/resume/{1}'.format(instance.student_user.id, filename)
class Resume(models.Model):
resume = models.FileField(upload_to=resume_path, blank=True, null=True)
pub_date = models.DateTimeField(default=timezone.now)
student_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
Then, I would like to allow a user to select one of their uploaded files in a later form. So, I need to be able to dynamically set the path to the directory containing that user's files similar to the dynamic way that I set the upload_path in the original model.
I've tried the following according to this link:
def resume_directory_path(instance):
# returns the path: MEDIA_ROOT/user_<id>/resume/
return 'user_{0}/resume/'.format(instance.student_user.id)
class JobApplication(models.Model):
student_user = models.ForeignKey(StudentUser, on_delete = models.CASCADE)
resume = models.FilePathField(path=resume_directory_path, null=True)
However, looking at the documentation for FilePathField in Django 3.0, it doesn't look like it takes a callable for the path attribute. So, I'm not sure how the answer in the above link answers my question. What is the best way to achieve this functionality?
I'd like to do something like the below:
class CallableFilePathField(models.FilePathField):
def __init__(self, *args, **kwargs):
kwargs['path'] = resume_directory_path(instance)
super().__init__(*args, **kwargs)
class JobApplication(models.Model):
student_user = models.ForeignKey(StudentUser, on_delete = models.CASCADE)
resume = models.CallableFilePathField(path=resume_directory_path, null=True)
The trouble is that I don't know how to properly reference the model instance in this code (so instance is undefined). I've looked at the FileField code to try to see how they do it there, but I couldn't make sense of it.

Related

Checking if a field in model is modified and creating instance of another model

I have two models
Project Model
class Project(models.Model):
name = models.CharField(max_length=200)
workflow = models.ForeignKey("WorkflowType", null=True, blank=True, on_delete=models.SET_NULL)
created_on = models.DateTimeField(auto_now_add=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
def __str__(self):
return self.name
Workflow Instance Model
class WorkflowInstance(models.Model):
workflow_step = models.ForeignKey('WorkflowStep', null=True, blank=True, on_delete=models.CASCADE)
project = models.ForeignKey('Project', null=True, blank=True, on_delete=models.SET_NULL)
I want to check if the value of workflow field in "Project" models is added or changed for a particular project.
I am approaching the problem in following manner:
Checking if the previous and the new value of the "workflow" field in a project are different. If yes (modifies), then create the new instance of a project.
#receiver(pre_save, sender=Project)
def projectToBeUpdated(sender, instance, **kwargs):
if instance.id is None:
pass
else:
previous = Project.objects.get(id=instance.id)
if previous.workflow != instance.workflow:
print("workflow value modified. Please create a WorkflowInstance")
Problem: The comparison for previous and new value of the "workflow" field are happening in "pre_save" signal. But my new instance creation for workflowInstance is to be created in "post_save" signal. How can I do this?
Also, ideally I would like to store the previous value of workflow field in "pre_save" and get the new value of the field in "post_save". Reason being, save() method might fail for any reason, while I am comparing the previous and new value in "pre_save" method itself. Making changes in the database without confirming if the save() method executed successfully would be a wrong approach in my view.
You can override the model save method itself and use the following code logic, additionally if you want to identify what fields got modified you can use the dirtyfields package as well.
def save(self, *args, **kwargs):
if not self._state.adding:
changed_attr = self.get_dirty_fields()
else:
is_new = True
super().save(*args, **kwargs)```

Django: How do I upload an image to a folder named after the article slug

I defined two models Article, Article_photos. Photos present in each article is stored in the relevant Article_photos model. I intend to store multiple photos per article in this way.
I want each photo to be uploaded to a folder by naming the folder in this manner: {slug}_images.
This is Article model:
class Article(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='Author')
article_title = models.TextField()
article_content = models.TextField()
slug = models.SlugField(max_length=30, blank=True)
...
def save(self, *args, **kwargs):
self.slug = slugify(self.article_title)
super(Article, self).save(*args, **kwargs)
And the Article_photos model to store one or more photos:
class Article_photo(models.Model):
article = models.ForeignKey('Article', on_delete=models.CASCADE)
photo = models.ImageField(upload_to='{}_images'.format(article.slug))
Now, when i'm running makemigrations, it's returning this error: AttributeError: 'ForeignKey' object has no attribute 'slug'.
How do rename the folder in the pattern I want?
You need to make use of a function here, for example:
def article_photo_location(self, filename):
return '{}_images/{}'.format(self.article.slug, filename)
class Article_photo(models.Model):
article = models.ForeignKey('Article', on_delete=models.CASCADE)
photo = models.ImageField(upload_to=article_photo_location)
Note that you should not call the function, you pass a reference of the function to the upload_to=… parameter [Django-doc].
Note: normally a Django models, just like all classes in Python are given a name in PerlCase, not snake_case, so it should be: ArticlePhoto instead of Article_photo.

Using reverse ForeignKey relation to get count of children for current object not working properly

I'm currently making a Todo app, I have multiple Todolist that can each contain multiple tasks, here's how I made my models :
class Todo(models.Model):
name = models.CharField(max_length=120)
total_tasks = models.IntegerField(default=0)
completed_tasks = models.IntegerField(default=0)
def update_total_tasks(self):
self.total_tasks = self.task_set.all()
def _str_(self):
return self.name
class Task(models.Model):
name = models.CharField(max_length=120)
completed = models.BooleanField(default=False)
todo = models.ForeignKey(Todo, on_delete=models.CASCADE, related_name="tasks")
My todo contains all current tasks as well as completed tasks, the update_total_tasks function is meant to query all tasks linked to that particular todo and update the field accordingly.
This function is called each time a task is created / updated with :
#receiver(models.signals.post_save, sender=Task)
def execute_after_save(sender, instance, created, *args, **kwargs):
instance.todo.update_total_tasks()
The receiver works and calls my update function properly though it seems the query is done the wrong way because I get this error :
AttributeError: 'Todo' object has no attribute 'task_set'
Do you have any idea on why it's not working properly ?
Thanks.
The related_name=… parameter [Django-doc] is set to 'tasks'. Hence you access the relation in reverse with .tasks.
You thus access this with:
class Todo(models.Model):
name = models.CharField(max_length=120)
total_tasks = models.IntegerField(default=0)
completed_tasks = models.IntegerField(default=0)
def update_total_tasks(self):
self.total_tasks = self.tasks.count()

Django - how to access a ForeignKey parent's attribute?

I'm a newbie in Django, and I don't know how to this.
I have a model 'Seller':
class Seller(models.Model):
seller_name = models.CharField(max_length=50)
def __str__(self):
return self.seller_name
and a model 'Item':
class Item(models.Model):
seller = models.ForeignKey(Seller, on_delete=models.CASCADE)
item_name = models.CharField(max_length=100)
item_category = models.CharField(max_length=100, choices=ALL_CATEGORIES)
item_price = models.FloatField()
item_preview = models.ImageField(upload_to='previews/<the seller's name>')
def __str__(self):
return self.item_name
connected via ForeignKey to Seller.
In this model, I have an ImageField, and I want it to upload the files to previews/Seller's name directory, but I don't know how to access the Seller's name from Item. Is it possible? Or am I doing something I am not supposed to? Because I couldn't find any similar cases in the internet.
You can access Seller name like this.
item = Item.objects.get(<condition>)
item.seller.seller_name
If you are using filter
items = Item.objects.filter(<condition>)
items[0].seller.seller_name #You need to provide index in queryset
or
for item in items:
item.seller.seller_name
you can't provide a path like that. You can either use a callable in upload_to or can use the lambda function.
item_preview = models.ImageField(upload_to=lambda instance: 'previews/{0}'.format(instance.seller.seller_name))
If you use upload_to callable
item_preview = models.ImageField(upload_to=upload_file_handler)
def upload_file_handler(instance, filename):
return 'previews/{0}'.format(instance.seller.seller_name)

Django: save foreign key according to string parameter?

Here is the thing.
A branch may contains many products.
A form would post path and branch to product model, in string format.
How could I use like this Product.objects.create(path="path1", branch="branch1") when got the posted data?
or the branch instance must be created in forms.py?
Here is the wrong version: it would raise ValueError: Cannot assign "'branch1'": "Product.branch" must be a "Branch" instance.
class Branch(models.Model):
name = models.CharField(max_length=63, unique=True, blank=True)
class Product(models.Model):
path = models.CharField(max_length=255)
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
kwargs['branch'], _ = Branch.objects.get_or_create(name=kwargs['branch'])
super(Product, self).save(*args, **kwargs)
This is not the save issue. The error raises during the assignment of string to branch name. If you want to implement the logic, do it before saving
You can use python property to achieve this, with small modifications as shown below. Don't need to override save method.
models.py
class Branch(models.Model):
name = models.CharField(max_length=63, unique=True)
class Product(models.Model):
path = models.CharField(max_length=255)
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
#property
def branch_name(self):
return self.branch.name
#branch_name.setter
def branch_name(self, value):
self.branch, _ = Branch.objects.get_or_create(name=value)
And your create function should be
Product.objects.create(path="path1", branch_name="branch1")
NOTE : It is branch_name and not branch. Also product.branch remains the branch object and poduct.branch_name returns the name of the branch. This will work with updation also. That is product.branch_name to new value updates the branch of the product