Accessing a value from another model in odoo - python

I have 2 simple models:
the first one is to create a product total
the other one is to calculate product total.
I want to get the product total value from the second model and pass first model how can I do that?
I want to get ans fields value from second model ans pass total field from first model
class MyinvoiceInvoice(models.Model):
_name = "myinvoice.invoice"
total = fields.Integer(string="Total",store=True)
class InvoiceLine(models.Model):
_name = "myinvoice.invoice.line"
_description = "myinvoice.invoice.line"
_inherit = "myinvoice.invoice"
customer_id = fields.Many2one('myinvoice.invoice', string='Customer Id')
product_id = fields.Many2one('myinvoice.product', string='Product')
quanitity = fields.Integer(string="Quanitity")
unit_price = fields.Integer(string="Unit Price",related='product_id.price')
line_total = fields.Integer(string="Line Total",compute='_compute_total')
ans = fields.Integer(string='ans')
#api.depends('quanitity')
def _compute_total(self):
check = 0
for record in self:
if record.quanitity:
record.line_total = record.unit_price * record.quanitity
check += record.line_total
else:
record.quanitity = 0
record.ans = check

I'm not sure if I understood your question accurately, but it seems that you are trying to get the ans field filled up based on whatever the total is. First of all find and get which field links both these model (in your case it is the customer_id field), probably there will be a Many2one relation between these two models. Using this related field (customer_id) you can user related fields to obtain the total into ans. It would be something like the following:
ans = fields.Integer(related=customer_id.total, string='ans')

Related

How to create multiple objects with different values at a time in django?

I need to create two models from a single template. Creating Product model is fine. The Product model has the ManyToOne relation with ProductVariant. But I got problem while creating ProductVariant model.
request.POST.getlist('names') this gives me the result like this ['name1','name2] and the same goes for all.
I want to create ProductVariant object with each values. How can I do this ? Also I think there is a problem while stroing a HStoreField. request.POST.getlist('attributes') gives the value like this ['a:b','x:z'] so I converted it into dictionary(but not sure it works).
UPDATE:
What I want is
attributes, names ... all will have the same number of items in the list.
For example if the name is ['a','b','c'] then weight will also have 3 values in the list [12,15,23] like this.
I want to create ProductVariant object 3 times since every list will have 3 items in the list. The first object will have field values from the list first item which is name=a,weight=12.. and for the second object values will be name=b, weight=15 like this.
How will it be possible? Or I should change the logic ? Any suggestions ?
models
class ProductVariant(models.Model):
name = models.CharField(max_length=255, blank=False, null=False)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
attributes = HStoreField()
price = models.FloatField(blank=False, null=False, default=0.0)
views
product = product_form.save()
attributes = request.POST.getlist('attributes')
names = request.POST.getlist('name')
up = request.POST.getlist('price')
weight = request.POST.getlist('weight')
print(names, 'names')
# converting attributes into the dictionary for the HStore field
for attribute in attributes:
attributes_dict = {}
key, value = attribute.split(':')
attributes_dict[key] = value
ProductVariant.objects.create(name=name,...) # for each value I want to create this.
Answer for update:
names = ['a', 'b', 'c']
weights = [12, 15, 23]
params = zip(names, weights)
products = [ProductVariant(name=param[0], weight=param[1]) for param in params]
ProductVariant.objects.bulk_create(products)
I disagree with this approach, but if you really want to do it this way, ziping would be the way as #forkcs pointed out.
I would use Django to help me as much as possible, before i get there, please make this change. float != money
class ProductVariant(models.Model):
name = models.CharField(max_length=255, blank=False, null=False)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
attributes = HStoreField()
price = models.DecimalField(blank=False, null=False, default=0, max_digits=6, decimal_places=2)
Once thats done, the form should look like this:
class ProductVariantForm(forms.ModelForm):
class Meta:
fields = ('name', 'product', 'attributes', 'price')
model = ProductVariant
ProductVariantFormSet = formset_factory(ProductVariantForm)
Note that I don't have to parse/clean/format attributes? Thats because Django did it for me ;)
And you can use it as follow IF you raname your fields and not use the same name multiple times: (instead of all your fields being called "attributes", you call them "form-X-attributes" where X is the number 0-infinity, example)
product = product_form.save()
formset = ProductVariantFormSet(data=request.POST)
if formset.is_valid():
instances = []
for form in formset:
if form.is_valid(): # this could probably be removed
instances.append(form.save())
For extra credit you can also do: (it shouldn't really matter)
product = product_form.save()
formset = ProductVariantFormSet(data=request.POST)
if formset.is_valid():
instances = []
for form in formset:
if form.is_valid(): # this could probably be removed
instances.append(form.save(save=False))
ProductVariant.objects.bulk_create(instances)
What do you gain? STANDARDS!!! AND compartmentalization! Everyone that knows Django knows what you did. All your clean logic will be placed in the right place (the form), and you'll be less error prone.
Ps. i wrote tests for you. https://gist.github.com/kingbuzzman/937a9d207bd937d1b2bb22249ae6bdb2#file-formset_example-py-L142
If you want more information on my approach, see the docs https://docs.djangoproject.com/en/3.1/topics/forms/formsets/
As for attributes, it could be reduced to one line like this:
attributes_dict = dict(map(lambda x: x.split(':'), attributes))
To create multiple objects you should either iterate and create one object at a time or use bulk_create:
for name in names:
ProductVariant.objects.create(name=name,...)
Or
ProductVariant.objects.bulk_create([ProductVariant(name=name) for name in names])
Best practice for this is using bulk_create method.
product_variants = [ProductVariant(name=name) for name in names]
ProductVariant.objects.bulk_create(product_variants)

How to update queryset value in django?

I have written a python script in my project. I want to update the value of a field.
Here are my modes
class News_Channel(models.Model):
name = models.TextField(blank=False)
info = models.TextField(blank=False)
image = models.FileField()
website = models.TextField()
total_star = models.PositiveIntegerField(default=0)
total_user = models.IntegerField()
class Meta:
ordering = ["-id"]
def __str__(self):
return self.name
class Count(models.Model):
userId = models.ForeignKey(User, on_delete=models.CASCADE)
channelId = models.ForeignKey(News_Channel, on_delete=models.CASCADE)
rate = models.PositiveIntegerField(default=0)
def __str__(self):
return self.channelId.name
class Meta:
ordering = ["-id"]
This is my python script:
from feed.models import Count, News_Channel
def run():
for i in range(1, 11):
news_channel = Count.objects.filter(channelId=i)
total_rate = 0
for rate in news_channel:
total_rate += rate.rate
print(total_rate)
object = News_Channel.objects.filter(id=i)
print(total_rate)
print("before",object[0].total_star,total_rate)
object[0].total_star = total_rate
print("after", object[0].total_star)
object.update()
After counting the total_rate from the Count table I want to update the total star value in News_Channel table. I am failing to do so and get the data before the update and after the update as zero. Although total_rate has value.
The problem
The reason why this fails is because here object is a QuerySet of News_Channels, yeah that QuerySet might contain exactly one News_Channel, but that is irrelevant.
If you then use object[0] you make a query to the database to fetch the first element and deserialize it into a News_Channel object. Then you set the total_star of that object, but you never save that object. You only call .update() on the entire queryset, resulting in another independent query.
You can fix this with:
objects = News_Channel.objects.filter(id=i)
object = objects[0]
object.total_star = total_rate
object.save()
Or given you do not need any validation, you can boost performance with:
News_Channel.objects.filter(id=i).update(total_star=total_rate)
Updating all News_Channels
If you want to update all News_Channels, you actually better use a Subquery here:
from django.db.models import OuterRef, Sum, Subquery
subq = Subquery(
Count.objects.filter(
channelId=OuterRef('id')
).annotate(
total_rate=Sum('rate')
).order_by('channelId').values('total_rate')[:1]
)
News_Channel.objects.update(total_star=subq)
The reason is that object in your case is a queryset, and after you attempt to update object[0], you don't store the results in the db, and don't refresh the queryset. To get it to work you should pass the field you want to update into the update method.
So, try this:
def run():
for i in range(1, 11):
news_channel = Count.objects.filter(channelId=i)
total_rate = 0
for rate in news_channel:
total_rate += rate.rate
print(total_rate)
object = News_Channel.objects.filter(id=i)
print(total_rate)
print("before",object[0].total_star,total_rate)
object.update(total_star=total_rate)
print("after", object[0].total_star)
News_Channel.total_star can be calculated by using aggregation
news_channel_obj.count_set.aggregate(total_star=Sum('rate'))['total_star']
You can then either use this in your script:
object.total_star = object.count_set.aggregate(total_star=Sum('rate'))['total_star']
Or if you do not need to cache this value because performance is not an issue, you can remove the total_star field and add it as a property on the News_Channel model
#property
def total_star(self):
return self.count_set.aggregate(total_star=Sum('rate'))['total_star']

How to join tables with multiple foreign keys and get filtered data in Django?

I have two models like this:
class Question(models.Model):
ques_id = models.IntegerField()
test_id = models.ForeignKey('exam.Test')
ques = models.TextField()
class UserAnswer(models.Model):
user = models.ForeignKey('exam.User')
test_id = models.ForeignKey('exam.Test')
ques_id=models.ForeignKey('exam.Question')
user_ans = models.TextField()
I need to execute this query to get the correct 'ques' field values.
SELECT A.ques_id, B.ques, A.user_ans FROM useranswer A
inner join question B on B.ques_id= A.ques_id and B.test_id =A.test_id
WHERE A.user_id=1 and B.test_id='101'
So far what I have done:
UserAnswer.objects.filter(test_id=test_id, user_id=user_id).values('ques_id', 'ques_id__ques','user_ans')
But it doesn't returning the right 'ques' field values because it doesn't considering the B.test_id =A.test_id section. How to retrieve it???
First of all, your field names are misleading. Do not
suffix foreign key fields with _id! Accessing them as attributes returns model instances and django provides the _id suffixed attributes to access the actual keys implicitly:
class Question(models.Model):
test = models.ForeignKey('exam.Test')
ques = models.TextField()
# is this supposed to be the primary key? Don't do that
# ques_id = models.IntegerField()
class UserAnswer(models.Model):
user = models.ForeignKey('exam.User')
ques = models.ForeignKey('exam.Question')
user_ans = models.TextField()
# This is redundant as ques belongs to a test already
# test = models.ForeignKey('exam.Test')
I assume you want to get all the answers for one user and one test:
UserAnswer.objects.filter(
ques__test_id=test_id,
user_id=user_id
).values('ques_id', 'ques__ques', 'user_ans')

how to store only one value on my django models?

below is my model
from django.db import models
LEVEL_CHOICES = (('beg','beginner'),('ind','inter'),('exp','expert'))
class scrap(models.Model):
subject = models.CharField(max_length=128,unique=True)
# level= models.CharField(max_length=128,unique=True)
level = models.CharField(max_length=128, choices=LEVEL_CHOICES)
time = models.IntegerField(unique=True)
def __str__(self):
return self.subject
is there anyway so that i can only store one value per class.what i am doing is supplying values to this model's objects through a form.so i want only once instance of this class.if another value is supplied through through the form,what i want is the old value to be replaced and new value to be stored.
I'm not sure why do you really just keep one object for your model, but it's pretty easy to achieve:
if scrap.objects.exists():
current_scrap = Scrap.objects.all()[0]
else:
current_scrap = Scrap()
current_scrap.subject = new_subject
current_scrap.level = new_level
current_scrap.time = new_time
current_scrap.save()

Django ORM annotate products with customer score

Consider the following Django model
Customer(models.Model)
name = models.CharField()
Product(models.Model)
name = models.CharField()
Score(models.Model)
points = models.IntegerField()
customer = models.ForeignKey(Customer)
product = models.ForeignKey(Product)
I would like to get a list of all products annotated with:
total score from all customers
score from the current customer only
I was able to get it working for the total score like this:
products = Product.objects.annotate(total_points = Sum('score__points')).all()
but I don't know how to add an annotation with only the current customer score (if the customer has reviewed the product, None otherwise). I want something like this:
cus = Cusomter.objects.get(pk=123)
products = Product.objects.annotate(total_points = Sum('score__points'),\
current_customer_points= (Score.points where customer=cus and product = this).all()
ids = SortedDict()
for p in Product.objects.annotate(total=Sum('score__points')):
ids[p.pk] = p
for score in Score.objects.filter(customer_id=1, product__in=ids.keys()):
ids[score.product_id].customer_score = score
for p in ids.values():
print p.total, '/', p.customer_score.points if hasattr(p, 'customer_score') else '-'

Categories

Resources