Django - uploading file to folder named with id of the object - python

I was trying to make it this way but instead of {{product.id}}, folder is named None.
I read some articles about that, and I found out that is because folder is being made before whole object. So how should I do that to make it work?
models.py:
def getImageURL(self, filename):
return "products_images/" + str(self.pk) + "/product_image.png"
class Product(models.Model):
name = models.CharField(max_length=200)
image = models.ImageField(upload_to=getImageURL, default="media/static/products_images/default.png" )
changed my funtion to:
def getImageURL(instance, filename):
return "products_images/{}/product_image.png".format(instance.id)
But it works only while edditing existing object. When I'm trying to make a new one id is set to None.
Edit:
ok, finally I did it this way:
def getFileNumber():
queryset = Product.objects.all().order_by('pk')
last = queryset.last()
last_id = last.id
file_number = last_id+1
return str(file_number)
def getImageURL(instance, filename):
return "products_images/{}/product_image.png".format(getFileNumber())
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
availability = models.IntegerField()
price = models.DecimalField(max_digits=5, decimal_places=2)
Probably it is not the best way to do that but it's very simple to understand so why not to use it.
Some debugging:
def getFileNumber():
queryset = Product.objects.all().order_by('pk')
if queryset:
last = queryset.last()
last_id = last.id
file_number = last_id+1
return str(file_number)
else:
file_number=1
return str(file_number)
def getImageURL(instance, filename):
path = os.path.join("products_images/{}/product_image.png".format(getFileNumber()))
print(path)
if os.path.isfile("media/" + path):
print("image with this id already exist, ")
os.remove("media/" + path)
return path
else:
return path
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
availability = models.IntegerField()
price = models.DecimalField(max_digits=5, decimal_places=2)

The following is the issue:
The instance doesn't have a primary key yet before it is created
When the instance gets saved in the database, then you can get the primary key
Maybe use signals, somehow? Do the logic it in the view after saving it?
Alternative option to pk:
You can generate a UUID for the object.
id = models.UUIDField(
primary_key = True,
default = uuid.uuid4,
editable = False)
Or alternatively:
Have a look at this package, you can set the uuid alphabet, length, and some more. This would allow you access to the id, for sure (same with uuid).
https://pypi.org/project/shortuuid/
id = ShortUUIDField(
length=16,
max_length=40,
prefix="id_",
alphabet="abcdefg1234",
primary_key=True,
)
Sidenote:
def getImageURL(self, filename): #Self would normally be defined as instance, but I suppose this is just semantics.

This is because the id is saved to the model after calling save(). The first guess would be to use save() to get the object in return. But save() does not return anything. So I put together a little example
class Thingy(models.Model):
name = models.CharField(
_('name'),
max_length=64,
)
def save(self, *args, **kwargs):
super(Thingy, self).save(*args, **kwargs)
print(self.pk)
My test was:
>>> t = Thingy(name="lol")
>>> t.save()
1
And it printed the primary key since save() mutades self. You could check if the pk exists before save(). If yes, just add the image path. If not. Execute save() first, manipulate the image path using pk, and save the object again. It is not elegant in any way, but I guess that is the only way.

Related

Property of a self instance in Django Models?

recently i have created a model for storing some states in my DB. It's very simple i only store id and name.
class PayType(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
class Meta:
ordering = ('-name',)
def __str__(self):
return "[{0}] {1}".format(self.id, self.name)
Here you can see a migration where i insert default values in the DB.
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('stockmanager', '0023_pagotipo_remove_carrito_unidades_totales_and_more'),
]
def insertPayType(apps, schema_editor):
PayType = apps.get_model('stockmanager', 'PayType')
PayType(name="Credit").save()
PayType(name="Debit").save()
PayType(name="Cash").save()
operations = [
migrations.RunPython(insertPayType)
]
As you can see, i'll have fixed rows in DB.
I'd like to add properties in the PayType Model, in order to have easy access the instances of the model. Like a static attribute in java.
class PayType(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
class Meta:
ordering = ('-name',)
def __str__(self):
return "[{0}] {1}".format(self.id, self.name)
# I'm looking for something like this
#property
def CREDIT(self):
return self.objects.get(id = 1)
# I also tried with this, same result :(
#property
def DEBIT(self):
return get_object_or_404(self, id = 2)
I tried to access in a diferent part of my code with this
class Pay(models.Model):
id = models.AutoField(primary_key=True)
order_id = models.IntegerField()
value = models.FloatField()
pay_type = models.ForeignKey(PayType, on_delete=models.CASCADE)
Pay(
order_id = order_id,
value = 100,
# I want this
pay_type = Pay.CASH #Error line
).save()
The thing is that i receive this error
Cannot assign "<property object at 0x00000138FEAC7F60>": "Pay.pay_type" must be a "PayType" instance.
Any idea?
Can't you just write your logic this way? It should work as indeed:
# I'm looking for something like this
#property
def CREDIT(self):
return PayType.objects.get(id = 1)
# I also tried with this, same result :(
#property
def DEBIT(self):
return get_object_or_404(PayType, id = 2)
You could also try to fetch PayType class using meta framework or try to catch it using type(self), I think it should work.

How can I make DRF Serializer create() function only create an entry that does not exist yet?

I have two tables, which are connected with each other through a cross table. (Recipes <--> Ingredients)
My Serializer works ok, I can send POST-Requests and it saves everything. The problem ist, that every time a new Recipe comes in with let just say the Ingredient "Milk" then my Serializer creates a new entry in my database named Milk, although I have an already existing entry "Milk" in my database.
How do I tell my Serializer to use the Id of an already existing entry instead of creating a new one every time for the cross table.
Here is how I thought I could fix it, but it clearly doesn't:
class RecipeIngredientSerializer(serializers.ModelSerializer):
ingredient = IngerdientSerializer()
class Meta:
model = recipe_ingredients
fields = ['amount', 'unit', 'ingredient']
def create(self, validated_data):
ingredient_validated_data = validated_data.pop('ingredient')
ingredient_serializer = self.fields['ingredient']
ingredientDict = dict(ingredient_validated_data)
// This is where I try to check if there is already an ingredient with the name from the form
ingredientObj = ingredient.objects.all().filter(ingredient_name=ingredientDict['ingredient_name']).
if not ingredientObj:
ingredient_instance = ingredient.objects.create(**ingredientDict)
validated_data['ingredient'] = ingredient_instance
else:
ingredient_instance = ingredient_serializer.create(ingredientDict)
validated_data['ingredient'] = ingredient_instance
recipe_ingredients_instance = recipe_ingredients.objects.create(**validated_data)
return recipe_ingredients_instance
This code also seems to work, at least I find an existing ingredient, but after the last create() it seems to ignore what ever I push into the validated_data['ingredient'] object.
EDIT
my models are:
class recipe_ingredients(models.Model):
recipe = models.ForeignKey(recipe, models.CASCADE)
ingredient = models.ForeignKey(ingredient, models.CASCADE)
amount = models.IntegerField(default=0)
unit = models.CharField(max_length=50)
def __str__(self):
return self.ingredient.ingredient_name + ' of Recipe: ' + self.recipe.recipe_name
class recipe(models.Model):
recipe_name = models.CharField(max_length=200)
assembly_time = models.IntegerField(default=0)
number_of_servings = models.IntegerField(default=0)
tags = models.ManyToManyField(tag, blank=True)
def __str__(self):
return self.recipe_name
class ingredient(models.Model):
ingredient_name = models.CharField(max_length=200)
ingredient_calories = models.IntegerField('Calories per 100 Units', default=-1)
default_unit = models.CharField(max_length=50)
def __str__(self):
return self.ingredient_name
I got the answer, finally. My mistake is this line in my Serializer:
ingredientObj = ingredient.objects.all().filter(ingredient_name=ingredientDict['ingredient_name']).
if not ingredientObj:
ingredient_instance = ingredient.objects.create(**ingredientDict)
validated_data['ingredient'] = ingredient_instance
I changed it now so that it looks something like this:
ingredientObj = ingredient.objects.all().filter(ingredient_name=ingredientDict['ingredient_name']).
if len(ingredientObj):
ingredient_instance = ingredientObj.first()
validated_data['ingredient'] = ingredient_instance
The ingredient.object.create(**ingredientDict) does actually create a new object (who would have known ;) ). This is probably still an ugly solution and I am open to more criticism but this does work for now.

Django model foreignkey queries

So i have this two models in django:
class Course(models.Model):
def get_image_path(self, filename):
return os.path.join('courses', str(self.slug), filename)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Course, self).save(*args, **kwargs)
name = models.CharField(max_length=255, verbose_name="Nombre")
description = models.CharField(max_length=255, verbose_name="DescripciĆ³n")
price = models.DecimalField(max_digits=12,decimal_places=2, verbose_name="Precio")
slug = models.SlugField(blank=True, max_length=255)
icon_img = models.ImageField(upload_to=get_image_path, blank=True, null=True, verbose_name="Imagen")
background_color = ColorField(default="#026085")
class Meta:
verbose_name = "curso"
verbose_name_plural = "cursos"
class UserCourse(models.Model):
user = models.ForeignKey(User)
course = models.ForeignKey(Course)
So whenever a user "buys" a course, it is stored in UserCourse. I have a view where the system shows a list of all the courses the user has bought. This is the view code:
def user_course_list_view(request, username):
context_dict = {}
try:
user_courses = UserCourse.objects.filter(user=request.user).course_set
context_dict['courses'] = user_courses
context_dict['heading'] = "Mis cursos"
except:
context_dict['courses'] = None
context_dict['heading'] = "Mis cursos wey"
return render(request, 'courses/course_list.html', context=context_dict)
I dont know where is the error and I cant seem to catch the exception (im using django with docker)
tl;dr
Something like this should work.
usercourse_objects = UserCourse.objects.filter(user=request.user).select_related('course')
user_courses = [x.course for x in usercourse_objects]
Explanation
There are multiple ways to do this, but one way would be to first get all the UserCourse objects for the current user:
usercourse_objects = UserCourse.objects.filter(user=request.user)
Then, for each UserCourse object, get the related Course:
user_courses = [x.course for x in usercourse_objects]
Now, the second line causes N database queries (one for each time we follow the course foreign key relation. To prevent this, the first line can be changed to:
usercourse_objects = UserCourse.objects.filter(user=request.user).select_related('course')
This pre-populates the course attribute of the UserCourse objects. More info about select_related() can be found here.

Basic Python, Django, DRY - calling a method from a (model) class

I'm new to Python and Django. I have a basic python/django ORM question that's bothering me. I have two models and they have a show_image function that's repeated. That's no good.
class Dinner(models.Model):
title = models.CharField(max_length=200)
is_approved = models.BooleanField()
hero = models.ImageField(upload_to="heros", blank=True)
def show_image(self):
image_url = None
if self.hero is not None:
image_url = """<img src="{0}{1}" />""".format(BASE_URL, self.hero)
return image_url
show_image.short_description = "Thumbnail"
show_image.allow_tags = True
class Speaker(models.Model):
title = models.CharField(max_length=200)
biography = models.TextField(blank=True)
headshot = models.ImageField(upload_to="headshots", blank=True)
def show_image(self):
image_url = None
if self.headshot is not None:
image_url = """<img src="{0}{1}" />""".format(BASE_URL, self.headshot)
return image_url
show_image.short_description = "Thumbnail"
show_image.allow_tags = True
Seems simple enough- I decided to start experimenting. I created a method in models.py...
def test(obj):
print obj
then in my models I tried:
test(self.hero)
and got this (instead of the value):
django.db.models.fields.files.ImageField
How do I get the value out of this so I can check if the ImageField has been populated?
edit:
class Speaker(models.Model):
title = models.CharField(max_length=200)
biography = models.TextField(blank=True)
headshot = models.ImageField(upload_to=upload_to, blank=True)
test(headshot)
def show_image(self):
image_url = None
if self.headshot is not None:
image_url = """<img src="{0}{1}" />""".format(BASE_URL, self.headshot)
return image_url
show_image.short_description = "Thumbnail"
show_image.allow_tags = True
You're calling that test method at class level, which makes no sense. That means it's executed at the time that the model class is defined, which is why you see the field class. There's a whole lot of metaclass stuff that happens when models are defined, so that when you get an instance you see the value, not the field class - but that hasn't happened at the point you're calling the method.
In any case, you need to call that with an instance of the model, so that there is actually a value to deal with.
I suspect you're fairly new to Python, so here's a tip: you can inspect all this stuff from the Python shell. Start ./manage.py shell, then import your models, instantiate one (or get it from the db), then you can examine it with dir() and so on. Much more efficient than writing debug functions in your code.

Django foreign key access in save() function

Here's my code:
class Publisher(models.Model):
name = models.CharField(
max_length = 200,
unique = True,
)
url = models.URLField()
def __unicode__(self):
return self.name
def save(self):
pass
class Item(models.Model):
publisher = models.ForeignKey(Publisher)
name = models.CharField(
max_length = 200,
)
code = models.CharField(
max_length = 10,
)
def __unicode__(self):
return self.name
I want to be able to access each Item from the Publisher save function. How can I do this?
For instance, I'd like to append text to the code field of each Item associated with this Publisher on the save of Publisher.
edit:
When I try to implement the first solution, I get the error "'Publisher' object has no attribute 'item_set'". Apparently I can't access it that way. Any other clues?
edit 2:
I discovered that the problem occurring is that when I create a new Publisher object, I add Items inline. Therefor, when trying to save a Publisher and access the Items, they don't exist.
Is there any way around this?!
You should be able to do something like the following:
def save(self, **kwargs):
super(Publisher, self).save(**kwargs)
for item in self.item_set.all():
item.code = "%s - whatever" % item.code
I don't really like what you're doing here, this isn't a good way to relate Item to Publisher. What is it you're after in the end?

Categories

Resources