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.
Related
Say I have a model Food
class Food(models.Model):
description = models.TextField()
toppings = models.TextField()
scheme = models.ForeignKey(FoodScheme, models.CASCADE)
And I want to have another class, a FoodScheme which describes which of the fields must be set in a specific Food class.
class FoodScheme(models.Model):
scheme_name = models.TextField()
requires_description = models.BooleanField(default=False)
requires_toppings = models.BooleanField(default=False)
But instead of hard coding this, I want to programmatically set these fields up, so any change in Food will change the FoodScheme class too.
An example implementation (that doesn't work, for several reasons, but I think gets my point across):
class FoodScheme(models.Model):
scheme_name = models.TextField()
for f in Food.get_fields():
setattr(self, f"requires_{f.name}", models.BooleanField(default=False))
Any ideas would be greatly appreciated.
As suggested by iklinac, the easiest way to get this to work is using a JSONField. This allows the content to be dynamic.
Here's an example GenericScheme:
class GenericScheme(models.Model):
scheme_name = models.TextField(default="Unnamed Scheme")
scheme_json = models.JSONField(default=dict)
model = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.scheme_json = {}
self.update()
def update(self):
if self.model is None:
return
for f in self.model._meta.get_fields():
if f.name not in self.scheme_json:
self.scheme_json[f.name] = False
I'm using the 3rd party library, Django Extensions, specifically for AutoSlugField().
I want to implement their use of RandomCharField() in my model for my slug field.
from django_extensions.db.fields import AutoSlugField, RandomCharField
class Post(models.Model):
class BucketObjects(models.Manager):
def get_queryset(self):
return super().get_queryset()
...
#unique_string = RandomCharField(length=10, unique=True)
slug = AutoSlugField(populate_from = models.random_string(), blank=True)
...
objects = models.Manager()
bucketobjects = BucketObjects()
def random_string(self):
unique_characters = RandomCharField(length=10, unique=True)
self.slug = unique_characters
self.save()
def __unicode__(self):
return self.stock_list
AutoSlugField requires the populate_from parameter. Passing RandomCharField() like so:
slug = AutoSlugField(populate_from = RandomCharField(), ...)
Does not work:
TypeError: 'populate_from' must be str or list[str] or tuple[str], found <django_extensions.db.fields.RandomCharField>
So instead I want to make a function within my class that creates and saves a RandomCharField to my slug field.
In the model above, you can see I commented out unique_string, if I pass that field to my populate_from parameter, then everything works fine. But it seems a redundant and a waste of space to have two model fields that are exactly the same.
How can I pass my random_string function to my slug populate_from parameter whenever a Post is created?
Thank you for the help.
You don't need to (or can't) use the RandomCharField(...) here. But, something like this will certainly work
from django_extensions.db.fields import AutoSlugField,RandomCharField
import string, random
class Post(models.Model):
slug = AutoSlugField(populate_from="random_string", blank=True)
def random_string(self):
length = 10
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))
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.
My question is how i can insert multiple keywords in one django field and show them in a template like stackoverflow tags.
Models:
class Jobs(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(blank=True, default='')
company = models.ForeignKey(Company, on_delete=models.CASCADE)
tags = ?????
Create another class and use many-to-many relationship between jobs class (tags) and new class:
class Tags(models.Model):
tag_name=models.CharField()
In jobs class
tags=models.ManyToManyField(Tags)
For show in template you can use for loop, etc.
Make it a Comma separated value.
class Jobs(models.Model):
tags = models.TextField()
def tag_list(self):
return self.tags.split(",")
def add_tag(self, tag_str):
current_tags = self.tag_list()
current_tags.append(tag_str)
current_tags = set(current_tags)
new_tag_string = ",".join(current_tags)
self.tags = new_tag_string
# you could save the model now or let caller save it outside of this method. I suggest letting caller save the model.
def remove_tag(self, tag_str):
current_tags = self.tag_list()
current_tags.remove(tag_str)
new_tag_string = ",".join(current_tags)
self.tags = new_tag_string
# you could save the model now or let caller save it outside of this method. I suggest letting caller save the model.
Whenever I create DRY functions that I can reuse later and then use them in models, I get circular references;
For example:
I have the following models:
from social.services import get_top_viewed_posts
class Post(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
title = models.CharField('Post Title', max_length=255)
class ActivityUpdateEmail(models.Model):
sent = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now = True)
def send(self):
posts = get_top_viewed_posts()
My top viewed posts function is another file called services.py so I can access it other places. It looks like:
from social.models import Post
def get_top_viewed_posts():
posts = Post.objects.filter(
pk__in=popular_posts_ids,
).order_by(
'-created_at'
)
return posts
Then I get the error:
services.py", line 1, in <module>
from social.models import Post
ImportError: cannot import name 'Post'
If I change it to:
transactions = Action.objects.filter(
content_type__pk=35,
created_at__gte=start_date,
).values_list('object_id', flat=True)
popular_posts_ids = []
popular_posts = Counter(transactions).most_common()[:result_amount]
for dic in popular_posts:
popular_posts_ids.append(dic[0])
class ActivityUpdateEmail(models.Model):
sent = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now = True)
def send(self):
posts = Post.objects.filter(
pk__in=popular_posts_ids,
).order_by(
'-created_at'
)
This works no problem.
How can I use this dry approach of abstracting functionality, and then being able to use them in my models?
The error occurs because when you import get_top_viewed_posts at the top of models.py the Post model is not declared yet.
You have a few alternatives.
Move the import from the top of models.py to inside the method
def send(self):
from social.services import get_top_viewed_posts
posts = get_top_viewed_posts()
Don't worry about performance, imports are cached - but if you use it in other methods it may be tedious to repeat the same import over and over.
Abstract the class
Make the function more generic passing the model as an argument, this way you don't need to import the model in the top of the services.py file:
def get_top_viewed_model(model, popular_ids, order_by='-created_at'):
return model.objects..filter(
pk__in=popular_ids,
).order_by(
order
)
Then:
def send(self):
posts = get_top_viewed_model(type(self), popular_posts_ids)
# at other places
get_top_viewed_model(Posts, popular_posts_ids)
Use a custom manager
Create a custom manager with a top_viewed method:
class TopViewedManager(models.Manager):
def __init__(self, order='-created_at', **kwargs):
self._order = order
self._filter = kwargs
def top_viewed(self):
return self.get_queryset().filter(**self._filter).order_by(self._order)
class Post(models.Model):
...
objects = TopViewedManager(pk__in=popular_posts_ids)
Then just use this where you would use get_top_viewed_model:
Post.objects.top_viewed()
This manager is quite generic so you can use it with any model, filter and order you want.
Probably there are other alternatives and it is a matter of personal taste.