I have 2 models:
Category(models.Model):
name = models.CharField(max_length=30)
no_of_posts = models.IntegerField(default=0) # a denormalised field to store post count
Post(models.Model):
category = models.ForeignKey(Category)
title = models.CharField(max_length=100)
desc = models.TextField()
user = models.ForeignKey(User)
pub_date = models.DateTimeField(null=True, blank=True)
first_save = models.BooleanField()
Since I always want to show the no. of posts alongwith each category, I always count & store them every time a user creates or deletes a post this way:
## inside Post model ##
def save(self):
if not pub_date and first_save:
pub_date = datetime.datetime.now()
# counting & saving category posts when a post is 1st published
category = self.category
super(Post, self).save()
category.no_of_posts = Post.objects.filter(category=category).count()
category.save()
def delete(self):
category = self.category
super(Post, self).delete()
category.no_of_posts = Post.objects.filter(category=category).count()
category.save()
........
My question is whether, instead of counting every object, can we not use something like:
category.no_of_posts += 1 // in save() # and
category.no_of_posts -= 1 // in delete()
Or is there a better solution!
Oh, I missed that! I updated the post model to include the relationship!
Yes, a much better solution:
from django.db.models import Count
class CategoryManager(models.Manager):
def get_query_set(self, *args, **kwargs):
qs = super(CategoryManager, self).get_query_set(*args, **kwargs)
return qs.annotate(no_of_posts=Count('post'))
class Category(models.Model):
...
objects = CategoryManager()
Since you didn't show the relationship between Post and Category, I guessed on the Count('posts') part. You might have to fiddle with that.
Oh, and you'll want to get rid of the no_of_posts field from the model. It's not necessary with this. Or, you can just change the name of the annotation.
You'll still be able to get the post count with category.no_of_posts but you're making the database do the legwork for you.
Related
I'd like to Sum the post_value of all of the Posts for each post_user to eventually use in a chart. I'm struggling with how to formulate the query?
So far, I've got to:
user_totals = User.objects.annotate(post_value_total=Sum('post'))
models.py
class User(AbstractUser):
pass
class Post(models.Model):
post_user = models.ForeignKey(User, on_delete=models.CASCADE)
post_cat = models.ForeignKey(Category, on_delete=models.CASCADE)
post_action = models.ForeignKey(Action, on_delete=models.CASCADE)
post_quantity = models.PositiveIntegerField(blank=True, null=True)
post_value = models.PositiveIntegerField(default='0')
post_timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.post_user}'s post at {self.post_timestamp}"
Thanks.
I'd like to Sum the post_value of all of the Posts for each post_user to eventually use in a chart.
Since each Post has a non-nullable post_user ForeignKey, it means that each Post belongs to exactly one user.
We thus can sum up the number of post_values of all Users with:
Post.objects.all().count()
If you only want to sum these up for a subset of the users, you can work with:
Post.objects.filter(
post_user__in=[user1, user2, user3]
).count()
or if you have ids:
Post.objects.filter(
post_user_id__in=[user_id1, user_id2, user_id3]
).count()
Or if you want to sum up the post_values, you can work with:
from django.db.models import Sum
total_post_value = Post.objects.aggregate(
total=Sum('post_value')
) or 0
The or 0 is necessary if the collection can be empty, since the sum of no records is NULL/None, not 0.
Or if you want to do this per User, we can work with:
user_totals = User.objects.annotate(
post_value_total=Sum('post__post_value')
)
The User objects that arise from this will have an extra attribute post_value_total that sums up the values of the related Posts. These can be None if a user has no related Posts. In that case we can work Coalesce [Django-doc]:
from django.db.models import Sum, Value
from django.db.models.functions import Coalesce
user_totals = User.objects.annotate(
post_value_total=Coalesce(Sum('post__post_value'), Value(0))
)
How can I use the first, let's say 10 characters of a field in the string representation of a Django model entry?
If I simply use {self.Post} I get the whole thing that might be too long. I tried to use {self.Post,10} but that doesn't really fly.
class Posts(models.Model):
Poster = models.ForeignKey(
User, on_delete=models.CASCADE, verbose_name="Poster")
PostCreated = models.DateTimeField(
auto_now_add=True, null=True, verbose_name="Post created")
Post = models.TextField(blank=True, verbose_name="Post")
PostEdited = models.BooleanField(
default=False, verbose_name="Has been edited")
PostHasComments = models.BooleanField(
default=False, verbose_name="Has comments")
def __str__(self):
return f"{self.Post} by {self.Poster}"
class Meta:
verbose_name_plural = "Posts"
You can slice the post, with:
def __str__(self):
return f'{self.Post[:10]} by {self.Poster}'
i have this model
class Person(models.Model):
picture = models.ImageField(
default='default.jpg', upload_to='profile_pics', )
firstName = models.CharField(blank=True, max_length=100)
familyName = models.CharField(blank=True, max_length=100)
age = models.IntegerField(default=0)
GENDER = [
("M", 'Male'),
("F", 'Female'),
("U", 'UNKNOWN'),
]
gender = models.CharField(
max_length=2,
choices=GENDER,
default="U",
)
address = models.CharField(blank=True, max_length=100)
remark = models.TextField(default="no remark")
description_vector = models.TextField(blank=True)
i want to infer the description_vector from the picture field (with a method called identifie(pictur) that return a string ) whenever i add a new Person or changing a model image (if the image didn't change i dont want to change the description_vector)
i know i can use the save method like here but i dont know how to specify that when the image change the vector change.
i dont know if it changes anything but i use django-rest-framowrk to add and change persons
i know
I am not sure I understand what your specific doubt is, but I think this might be helpful.
def save(self, *args, **kwargs):
if self.id is not None: # check only when update
original_picture = self.objects.get(id=self.id).picture
if original_picture !== self.picture # You must add here your method to evaluate if both images are equal
self.vector = some_method_to_change_vector(self.picture)
return super().save(*args, **kwargs)
Fellows,
As my system is becoming complex, I need help to think how to implement complex behavior. I will explain:
I have three models: Purchase, PurchaseDetails, and Stock.
The models is as follows:
class Purchase(models.Model):
def __str__(self):
return self.parceiro_pj.razao_social + ' em ' + str(self.data) + ": " + str(self.soma)
parceiro_pj = models.ForeignKey(ParceiroPJ,blank=True, null=True)
createdat = models.DateTimeField()
data = models.DateField(auto_now=True)
soma = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
class PurchaseDetails(models.Model):
def __str__(self):
return str(self.parceiro_pj_insumo.insumo.nome) + ' - ' + str(self.quantidade) + ' - ' + str(self.createdat)
purchase = models.ForeignKey(Purchase)
parceiro_pj_insumo = models.ForeignKey(ParceiroPJInsumo)
quantidade = models.IntegerField(blank=False, null=False)
createdat = models.DateField(auto_now=True)
def save(self, *args, **kwargs):
super(PurchaseDetail, self).save(*args, **kwargs) # Call the "real" save() method.
PurchaseDetail.objects.filter(Purchase=self.purchase.id).update(createdat=self.purchase.createdat.date())
itens = PurchaseDetail.objects.filter(compra=self.purchase.id)
valor = 0
for item in itens:
valor = valor + (item.parceiro_pj_insumo.preco * item.quantidade)
no_estoque = Estoque.objects.get(insumo=item.parceiro_pj_insumo.insumo.id)
unidade = Unidade.objects.get(nome=item.parceiro_pj_insumo.insumo.unidade.nome)
qt = no_estoque.quantidade + item.quantidade
volume_peso = no_estoque.volume_peso + (item.quantidade * item.parceiro_pj_insumo.insumo.volume_peso)
Stock.objects.filter(insumo=item.parceiro_pj_insumo.insumo).update(quantidade=qt, unidade=unidade, volume_peso=volume_peso, updatedat=self.purchase.createdat.date())
Purchase.objects.filter(pk=self.purchase.id).update(soma=valor)
Purchase.objects.filter(pk=self.purchase.id).update(data=self.purchase.createdat.date())
class Stock(models.Model):
def __str__(self):
return str(self.insumo.nome) + '(' + str(self.insumo.marca.nome) + ')' + ' - ' + str(self.quantidade)
insumo = models.OneToOneField(Insumo)
quantidade = models.IntegerField(blank=True, null=True)
unidade = models.ForeignKey(Unidade, blank=True, null=True)
volume_peso = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
updatedat = models.DateField(auto_now=False, blank=True, null=True)
There are related:
a) One Purchase has Many PurchaseDetails; b) Every PurchaseDetail has an effect on Stock. For every PurchaseDetail operation (insert, change, delete) the Stock must be updated.
I figure it out how to make the change on Stock every time a PurchaseDetail is inserted. But for the DELETE and UPDATE, the logic seems to be much more complex.
If I edit PurchaseDetails, the way its made, if I already had an item in the database, it will be calculated again when I save the PurchaseDetails (this model is embbeded with the Purchase form at admin), causing error and updating again the Stock.
I dont know how to implement the right way of doing it.
I have to check if its a new item on PurchaseDetail, or if it is a already existent register before update Stock, I dont know how to do it. Also, I dont know hot tom implement the case when I have to delete PurchaseDetails, and decrease items on Stock.
Anyone can help, please?
You may consider exploring the functionality provided by Django signals, which will provide you with a nice way to implement this type of behavior. Signals can be sent by built-in model methods like __init__() and save(), and are useful when different areas of your application might want to use information about these types of events.
Some of Django's built-in signals include pre_save, post_save, pre_delete, post_delete, etc; it is also straightforward to define and send your own signals.
Looking at a simplified example of what you already have, lets say you wanted to update a particular Stock every time a related PurchaseDetail was saved, for some reason. You might define a method like:
def update_stock(sender, instance, **kwargs):
purchase_details_id = instance.id
# do something here, like update and save a stock
post_save.connect(update_stock, sender=PurchaseDetail)
update_stock is the receiver function which will be called after the save of the PurchaseDetail object.
For further reference:
https://docs.djangoproject.com/en/1.10/ref/signals/
I have a very big model in models.py:
simplified version is:
class MyModel(models.Model):
item_1 = models.FloatField(null=True, blank=True)
...
item_20 = models.FloatField(null=True, blank=True)
in views.py:
def form_valid(self, form_class):
instance = form_class.save(commit=False)
for i in range(1, 20):
name = 'item_' + str(i)
instance.name = i
With this the field name 'item_1' ... to 'item_20' in instance is not recogniced. Instead 'name' is added to instance like other new field...
How can I iterate and save my model?
Any suggestion?
Thanks!!!
You should probably use setattr in order to loop through the fields and set the values in them. Try this:
def form_valid(self, form_class):
instance = form_class.save(commit=False)
for i in range(1, 20):
name = 'item_' + str(i)
setattr(instance, name, value) # Where value is the data you wanted to save in the field `name`
Similary user getattr() to get the data by looping through the class instance.