In my model, client_id must be one per product. So I want to make a constraint for this situation.
class ClientSystemCode(models.Model):
_name = 'client.system.code'
_description = 'Client System Code'
client_id = fields.Many2one('res.partner', 'Client')
product_id = fields.Many2one('product.template', 'Product')
client_sys_code = fields.Char('Client system code')
in product.template model my constraints look like this.
#api.constrains('client_system_code_ids')
def _client_system_code_constraint(self):
duplicates = []
for line in self.client_system_code_ids:
if line.client_id.id not in duplicates:
duplicates.append(line.client_id.id)
else:
raise ValidationError(_("Product can't have more than one client code with same client"))
it is ok as it triggered from product form view and there always be not that much lines. But constraint in client.system.code should be better performance wise because there can be thousands of lines. So is there any kind of better solutions?
You will get it done easily using an sql constraint like:
class ClientSystemCode(models.Model):
_name = 'client.system.code'
_sql_constraints = [
('client_product_unique', 'unique (client_id, product_id)', 'Product can't have more than one client code with same client'),
]
Related
Due to the structure of my project, I need to have multiple aggregations over three interlocked tables. With structure looking somewhat like this:
class ItemMeta(models.Model):
item = models.ForeignKey(
Item, on_delete=models.SET_NULL, null=True
)
class = models.CharField(max_length=2048, null=True, blank=True)
department = models.CharField(max_length=2048, null=True, blank=True)
class Item(models.Model):
amount = models.DecimalField(max_digits=18, decimal_places=2)
status = models.CharField(max_length=2048, null=True, blank=True, choices=ItemStatus.choices)
class CartItem(models.Model):
author = models.ForeignKey(to=UserModel, on_delete=model.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
class ItemStatus(models.TextChoices):
UNDER_CONSIDERATION = 'UNDER_CONSIDERATION', 'Under consideration'
IMPOSED = 'IMPOSED', 'Imposed'
PAID = 'PAID', 'Paid'
And I need to have item grouping by class, department and status both in cart and outside of it. I also need to have aggregations of combined amounts of items in different statuses, as well as counts of different items in cart and existing. So the structure of the response has to always contain 5 values: sum of paid, imposed and considered items, and count of items existing and in cart of the calling user. I inherited from last poor sod this piece of code to do these:
def _sum(self, status):
return Coalesce(Sum('amount', filter=Q(status=status)), 0.0, output_field=FloatField())
def annotate_kwargs(self):
return {
'under_consideration_amount': self._sum(ItemStatus.UNDER_CONSIDERATION),
'imposed_amount': self._sum(ItemStatus.IMPOSED),
'paid_amount': self._sum(ItemStatus.PAID),
'count': Count('pk', distinct=True),
'in_cart': Count('pk', distinct=True, filter=Q(cartitem__author=self.user)),
}
def get(self):
return self.queryset \
.values(*self.group_by) \
.annotate(**self.annotate_kwargs())
Which basically takes the Item queryset and groupes it according to request and then annotates it. Problem is, it returns lies, as is highlighted in the docs. Methinks having 3 different tables has something to do with it, but at this point i have no way to change the model structure, so it has to stay as it is or have as little change as possible. My question is how to have these aggregations? I tried using subquery, but i don't know how to make it work with .values clause
I am doing rating system on my current django project and I need to implement database for rating. Database would be simple, if I didn't have multiple rating criteries, now I have to ask:
How would you design a database with these objects:
User
RatedObject
Rating
there will ofcourse be multiple users and objects to be rated AND multiple rating criterias.
Now my current idea would be to go for few separate tables with each of the objects such as:
USER(pk=id, fk= rating.id)
RATED_OBJECT(pk=id, fk= rating.id, overall_attribute_1, overall_attribute_2, overall_rating)
RATING(pk=user.id, pk=rated_object.id, fk=rated_attribute1, fk=rated_attribute2)
RATED_ATRIBUTE(pk=id, fk=type, value)
TYPE(pk=id, name) - 2 types since we have 2 attributes to be rated
(now overal_rating will be average of all overall attributes and each overal attribute will be average of all attributes of one type from ratings, where the id of rated object will be same)
I have a bad feeling about doing this 'multiple-FK-to-one-PK' operation. Would it make more sence to make table for each rated attribute? Or maybe say **** it and have values in the RATING itself and screw RATED_ATTRIBUTE and TYPE table? What do you guys think?
EDIT: IF my guess was right- meaning I could connect RATING with RATED_ATTRIBUTE multiple times (multiple fk to 1 pk), I need to set constraints, so when rating happens, it all goes to fields of RATING correctly
(when rating attribute one, in RATING i need to ensure that in RATED_ATTRIBUTE the type is exactly the one like in RATING). Now, I cannot get my head arround, how to do that whidj Django's CheckConstraint, any ideas?
snippet:
class RatedAttribute(models.Model):
TYPE = (
('attribute1', 'attribute1'),
('attribute2', 'attribute2'),
)
id = models.IntegerField(primary_key= True, auto_created=True)
type = models.CharField(max_length=120, choices=TYPE, default=None)
value = models.IntegerField()
class Rating(models.Model):
user_id = models.ManyToManyField(User, through='RatingUser')
rated_object_id = models.ManyToManyField(RatedObject, through='Rating_Object')
attribute1 = models.ForeignKey(RatedAttribute, on_delete=models.CASCADE)
attribute2 = models.ForeignKey(RatedAttribute, on_delete=models.CASCADE)
class Meta:
constraints = [
CheckConstraint(
*NO IDEA HOW TO FILL THIS CONSTRAINT(s)*, name='something')
]
def __str__(self):
return self.place
code is with proper connecting Rating_Object
This is what i came up with after reading what you put in the comments.
If anything comment on this answer.
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
rating_choices = [
(1, '1 Star'),
(2, '2 Stars'),
(3, '3 Stars'),
(4, '4 Stars'),
(5, '5 Stars'),
]
class Product(models.Model):
class Meta:
verbose_name = "Product"
verbose_name_plural = "Products"
name = models.CharField(max_length=50)
description = models.TextField()
def __str__(self):
return self.name
class Rating(models.Model):
class Meta:
verbose_name = "rating"
verbose_name_plural = "ratings"
user = models.ForeignKey(User, verbose_name=_(""), on_delete=models.CASCADE, related_name="rating")
product = models.ForeignKey(Product, verbose_name=_(""), on_delete=models.CASCADE, related_name="product_rating")
taste = models.IntegerField(choices=rating_choices)
smell = models.IntegerField(choices=rating_choices)
def __str__(self):
return "{self.user.username}'s rating for {self.product.name}"
Hi I created the following model:
class PrescriptionsPrescriptions(models.Model):
_name = 'prescriptions.prescriptions'
name = fields.Many2one('res.users','Name', default=lambda self: self.env.user, readonly=True)
Date_entered = fields.Date(string='Date', default=fields.Date.today())
paper_prescriptions = fields.Selection([('yes', 'Yes'), ('no', 'No')], string='Paper Prescriptions?')
However I cannot get the _sql_constraints to work:
_sql_constraints = [('log_unique','unique(name,Date_entered)','You have already logged data for that date.')]
I'm trying to get it so that each person can log only one prescription per Date_entered. Would greatly appreciate any help. :)
Odoo will not be able to add the constraint because of a psycopg2.ProgrammingError
column "date_entered" named in key does not exist
You just need to rename it to date_entered and update the module.
First you can’t have capital letters as starting of your variable.
Second the only reason constraints don’t work after updating is when there is already data violating the constraints
I've added a field to product.template, source_id, via:
class ProductTemplate(models.Model):
_inherit = 'product.template'
source_id = fields.Integer('Original Source ID', help="The original source ID", index=True, required=False)
Right now my SQL DELETE queries on product.product are taking forever because source_id isn't an actual indexable field on product.product.
What I would like to do is inherit this same field on product.product, so that I can search product.product by source_id and perform large SQL DELETE queries on product.product.
Any suggestions for how to approach this?
An alternative solution would be to figure out a way to destroy dependent product.product records when DELETE is run on product.template. I'm not sure how to do this in Odoo/Postgres.
Please use the below code:
class ProductProduct(models.Model):
_inherit = 'product.product'
source_id = fields.Integer('Original Source ID',related='product_tmpl_id.source_id', help="The original source ID")
Here is my model class and a function. I would only like to add new animals to my list.
class Animals(models.Model):
id = models.AutoField(primary_key=True)
name = models.TextField(unique=True)
class Meta:
db_table = 'animals'
animals = []
for name in ['duck', 'cow', 'cat', 'dog']:
if (THIS ALREADY EXISTS) animals.objects.filter(name=name): # This will throw an error, because its a list
continue
else:
animal = Animals(name=name)
animals.append(animal)
Keep in mind I do not want to save these models to the database yet, but it would be great to query my list of models stored in memory as if they were all saved as a database table.
you can iterate over list of animals and do anything with them.
but be aware of all instances not saved in database have no pk attribute
but you can not filter them by model query
You can do something like this. Please use .exists()
animals = ['dog', 'cat', 'dragon', 'bear']
new_animals = []
for animal in animals:
if Animals.objects.filter(name=animal).exists():
continue
else:
new_animal = Animal(name=animal)
new_animals.append(new_animal)
or briefly:
new_animals = [Animals(name=animal) for animal in animals if not Animals.objects.filter(name=animal).exists()]