Is there anything like unique_together(max_occurences=3)? - sql

I have a model:
class MyModel(models.Model):
a = models.IntegerField()
b = models.IntegerField()
c = models.IntegerField()
Now, I need something like unique_together(a,b, max_occurences=3) constraint to be added to the above model (so that there can be up to 3 values of c for each pair of (a,b), and ideally those 3 values of c should be also unique for given (a,b)), but I don't know what to look for (and if something like this even exists in MySQL). Is there anything like that, or I have to do something like this:
class MyModel(models.Model):
a = models.IntegerField()
b = models.IntegerField()
c1 = models.IntegerField()
c2 = models.IntegerField()
c3 = models.IntegerField()
class Meta:
unique_together = ('a', 'b')
-- and handle c1..c3 myself?

You should override the save() method for the model and check your constraint before each save and raise a ValueError if the constraint is violated.
class MyModel(models.Model):
a = models.IntegerField()
b = models.IntegerField()
c = models.IntegerField()
def save(self):
try:
# Check values in model here
except:
raise ValueError("Cannot save more than 3 Cs with an A")
super(MyModel, self).save(*args, **kwargs)

Related

Get value from a Many2one related model in model create function

I have two models, TextMessage and Device, that are related many TextMessages to one Device.
from odoo import models, fields, api
class Device(models.Model):
_name = 'device'
_description = 'A model for storing all devices'
name = fields.Char()
iden = fields.Char()
model_name = fields.Char()
manufacturer = fields.Char()
push_token = fields.Char()
app_version = fields.Integer()
icon = fields.Char()
has_sms = fields.Char()
text_message_ids = fields.One2many("text_message", "device_id", string="Text Messages")
from odoo import models, fields, api
class TextMessage(models.Model):
_name = 'text_message'
_description = 'Text Messages'
name = fields.Char()
message_text = fields.Text()
pb_response = fields.Text()
target_number = fields.Char()
device_id = fields.Many2one('device', 'Device', required=True)
#api.model
#api.depends("device_id")
def create(self, values):
print("values['device_id']",values["device_id"])
print("self.device_id",self.device_id.iden)
for rec in self.device_id:
print("Device ID",rec.iden)
values['pb_response'] = rec.device_id.iden
return super().create(values)
In the create method of TextMessage, I want to retrieve the value of the iden attribute of the Device model.
The print statements in TextMessage.create print:
values['device_id'] 1
self.device_id False
The print statement in the loop prints nothing.
You can't access self before creating the record so it will be false.
You can write the create method in two ways:
Create the record first and then get the iden value:
#api.model
def create(self, values):
res = super().create(values)
res.pb_response = res.device_id.iden
return res
Or you can get the device_id record from values as below:
#api.model
def create(self, values):
if 'device_id' in values and values.get('device_id',False):
device = self.env['device'].browse(values.get('device_id'))
if device:
values['pb_response'] = device.iden
return super().create(values)
If the pb_response field is the same of the iden field then you can create it as related field to device_id.iden and you will get the iden value automatically once the device-id assigned as below:
pb_response = fields.Char(related="device_id.iden")

DRF- how to do many readonly and writeonly fields in serializers

I used two foreignkey in my model. I want to show those fields name when we give get request I have tried but its worked only one fields not rest one.
models.py
class Organization(models.Model):
code = models.CharField(max_length=25, null=False, unique=True)
name = models.CharField(max_length=100, null=False)
location = models.ForeignKey(Location, on_delete=models.RESTRICT)
mol_number = models.CharField(max_length=100)
corporate_id = models.CharField(max_length=100)
corporate_name = models.CharField(max_length=100)
routing_code = models.CharField(max_length=100)
iban = models.CharField(max_length=100)
description = models.TextField()
total_of_visas = models.IntegerField(null=False, default=0)
base_currency = models.ForeignKey(Currency, on_delete=models.RESTRICT)
def __str__(self):
return self.name
serializers.py
class OrganizationSerializer(serializers.ModelSerializer):
location = serializers.CharField(read_only=True, source="location.name")
base_currency = serializers.CharField(read_only=True, source="base_currency.currency")
location_id = serializers.IntegerField(write_only=True, source="country.id")
base_currency_id = serializers.IntegerField(write_only=True, source="base_currency.id")
class Meta:
model = Organization
fields = ["id", "name", "location", "mol_number", "corporate_id", "corporate_name",
"routing_code", "iban", "description", "total_of_visas", "base_currency",
"location_id", "base_currency_id"]
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
How can I access those two fields???.. Anyhelp Appreciable..
you can override the to_representation method
def to_representation(self, instance):
....
so something like this:
class OrganizationSerializer(serializers.ModelSerializer):
...
class Meta:
...
def to_representation(self, instance):
rep = super(OrganizationSerializer, self).to_representation(instance)
rep['location'] = instance.location.name //the .name is the field in the //location model that you want to return it can be anything in the model
rep['base_currency'] = instance.base_currency.currency
rep['location_id'] = instance.location_id.country.id
rep['base_currency_id'] = instance.base_currency_id.base_currency_id
return rep
def create(self, validated_data):
...
def update(self, instance, validated_data):

Convert SQL query in Django model format

I'm trying to convert an SQL query into django format but as I'm quite new to django I'm having some trouble.
My query:
select make_name, count(*) as count from main_app_make as make
join main_app_model as model on make.id = model.make_id
join main_app_vehicle as vehicle on model.id = vehicle.model_id
group by make.make_name
The result:
Audi 1
Mercedes-Benz 2
My models:
class Make(models.Model):
make_name = models.CharField(max_length=50)
make_logo = models.CharField(max_length=400)
def __str__(self):
return self.make_name
class Model(models.Model):
model_name = models.CharField(max_length=50)
make = models.ForeignKey(Make, on_delete=models.CASCADE)
def __str__(self):
return self.model_name
class Vehicle(models.Model):
type = models.ForeignKey(VehicleType, on_delete=models.CASCADE)
model = models.ForeignKey(Model, on_delete=models.CASCADE)
body_type = models.ForeignKey(Body, on_delete=models.CASCADE)
...
This is what I tried:
options = Make.objects.all().values('make_name').annotate(total=Count('make_name'))
I think you need to include the children models in the Count :
options = Make.objects.values('make_name').annotate(total=Count('model_set__vehicle_set'))
Reference : https://docs.djangoproject.com/en/stable/topics/db/aggregation/#following-relationships-backwards

Django admin: programatically choose visible fieldsets in inlines

What I want is to be able to choose which formsets to display according to the selection of a ForeignKey selector in the django Admin.
I have four models:
Product
Rating
Criterion (question)
CriterionAnswer
My database is organised as follows:
Product <- ManyToOne -- Rating <- ManyToMany -> Criterion -
| |
-- OneToMany -> CriterionAnswer <- OneToMany --
When I select a Rating in the Product model form of the admin and save the Product, I create all the missing CriterionAnswers that correspond to the Criterions in the chosen Rating. The next time I open the Product, I see the CriterionAnswers as inlines. If I then select another Rating, other CriterionAnswers will be saved and displayed along the old ones. How do I choose to display only the CriterionAnswers which correspond to the chosen Rating?
Models:
################
# Product
################
class Product (models.Model):
rating = models.ForeignKey(
'Rating',
on_delete=models.SET_NULL,
null=True,
)
def save(self, *args, **kwargs):
super(Product, self).save(*args, **kwargs)
# get all criteria that need an answer in order to create the rating
criteriaToAnswer = Criterion.objects.filter(rating=self.rating)
# iterate over all criterias and check if the answer exists in database
for crit in criteriaToAnswer:
# if it doesn't exist, create an answer
if not CriterionAnswer.objects.filter(criterion=crit).filter(product=self):
print "Criteria answer to: <%s> does not exist... creating new one" % crit.__str__()
new_criterionAnswer = CriterionAnswer()
new_criterionAnswer.criterion = crit
new_criterionAnswer.product = self
new_criterionAnswer.save()
else:
print "Criteria answer to: <%s> exists." % crit.__str__()
################
# CriterionAnswer
################
class CriterionAnswer(models.Model):
criterion = models.ForeignKey('Criterion', on_delete=models.CASCADE)
product = models.ForeignKey('Product', on_delete=models.CASCADE)
################
# Rating
################
class Rating(models.Model):
name = models.CharField(max_length=30)
################
# Criterion
################
class Criterion(models.Model):
category = models.ForeignKey(
'Category',
on_delete=models.PROTECT,
)
rating = models.ManyToManyField(
'Rating',
through='CriterionInRating',
through_fields=('criterion', 'rating'),
)
Admin:
class ProductAdmin(admin.ModelAdmin):
inlines = [
# Something here...
]
# Or something here that could solve my problem.

Join after outerjoin in SQLAlchemy

Suppose I have a one-to-many relationship, where the parents and children are grouped by some group_id.
Note: this example is a stripped down version of my code, which is actually a many-to-many relationship. There may be some errors unrelated to the question.
class Node(Base):
__tablename__ = 'node'
id = Column(GUID, default=uuid.uuid4, primary_key=True)
group_id = Column(GUID, nullable=False, primary_key=True)
parent_id = Column(GUID)
title = Column(Text, nullable=False)
class Leaf(Base):
__tablename__ = 'leaf'
id = Column(GUID, nullable=False, primary_key=True)
group_id = Column(GUID, nullable=False, primary_key=True)
parent_id = Column(GUID, nullable=False)
The group_id is used as a way to create new versions - so nodes and leaves with the same id can exist in multiple groups.
What I want to do is compare two groups, and find all the leaves whose parents have changed. I am trying to use an outer join to do the comparison, and then two joins to filter the parent nodes:
def find_changed_leaves(group_id_a, group_id_b, session):
NodeA = model.Node
NodeB = aliased(model.Node, name='node_b')
LeafA = model.Leaf
LeafB = aliased(model.Leaf, name='leaf_b')
query = (session.query(LeafA, LeafB)
.outerjoin(LeafB, LeafA.id == LeafB.id)
.join(NodeA, (LeafA.group_id == NodeA.group_id) &
(LeafA.parent_id == NodeA.id))
.join(NodeB, (LeafB.group_id == NodeB.group_id) &
(LeafB.parent_id == NodeB.id))
# Group membership
.filter(LeafA.group_id == group_id_a,
LeafB.group_id == group_id_b)
# Filter for modified parents
.filter(NodeA.title != NodeB.title)
)
return query.all()
This works, but it doesn't show leaves that are only in one of the groups (e.g. if a leaf was added to a node in the new group). How can I show all the leaves, returning None for a leaf that is missing from one of the groups?
Edit: I see there are perils mixing join with outer join. I tried naively changing it to .outerjoin(NodeA, ..., but it didn't help.
As mentioned in the comment, it is not entirely clear what needs to be achieved. Nonetheless, the code below should at least give you some directions.
First of all, I would not try to combine it all in one query (potentially using full joins and subqueries), but split it into 3 separate queries:
get LeafA, LeafB whose' parents have changed
get LaefA that do not have corresponding LeafB
get LaefB that do not have corresponding LeafA
Below is the code which should run as it is in both sqlite and postgresql. Note that I have added relationships and use them in the queries. But you could do the same with explicit join conditions as in your code snippet.
import uuid
from sqlalchemy import (
create_engine, Column, Integer, String, ForeignKey, Text, and_,
ForeignKeyConstraint, UniqueConstraint, exists
)
from sqlalchemy.orm import sessionmaker, relationship, eagerload, aliased
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.dialects.postgresql import UUID as GUID
_db_uri = 'sqlite:///:memory:'; GUID = String
# _db_uri = "postgresql://aaa:bbb#localhost/mytestdb"
engine = create_engine(_db_uri, echo=True)
Session = sessionmaker(bind=engine)
Base = declarative_base(engine)
newid = lambda: str(uuid.uuid4())
# define object model
class Node(Base):
__tablename__ = 'node'
id = Column(GUID, default=newid, primary_key=True)
group_id = Column(GUID, nullable=False, primary_key=True)
# parent_id = Column(GUID)
title = Column(Text, nullable=False)
class Leaf(Base):
__tablename__ = 'leaf'
id = Column(GUID, nullable=False, primary_key=True)
group_id = Column(GUID, nullable=False, primary_key=True)
parent_id = Column(GUID, nullable=False)
title = Column(Text, nullable=False)
# define relationships - easier test data creation and querying
parent = relationship(
Node,
primaryjoin=and_(Node.id == parent_id, Node.group_id == group_id),
backref="children",
)
__table_args__ = (
ForeignKeyConstraint(
['parent_id', 'group_id'], ['node.id', 'node.group_id']
),
)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
session = Session()
g1, g2, l1, l2, l3 = [newid() for _ in range(5)]
# Create test data
def _add_test_data():
n11 = Node(
title="node1", group_id=g1,
children=[
Leaf(id=l1, title="g1 only"),
Leaf(id=l3, title="both groups"),
]
)
n21 = Node(
title="node1 changed", group_id=g2,
children=[
Leaf(id=l2, title="g2 only"),
Leaf(id=l3, title="both groups"),
]
)
session.add_all([n11, n21])
session.commit()
def find_changed_leaves(group_id_a, group_id_b):
"""
Leaves which are in both versions, but a `title` for their parents is changed.
"""
NodeA = aliased(Node, name='node_a')
NodeB = aliased(Node, name='node_b')
LeafA = aliased(Leaf, name='leaf_a')
LeafB = aliased(Leaf, name='leaf_b')
query = (
session.query(LeafA, LeafB)
.filter(LeafA.group_id == group_id_a)
# #note: group membership for LeafB is part of join now
.join(LeafB, (LeafA.id == LeafB.id) & (LeafB.group_id == group_id_b))
.join(NodeA, LeafA.parent)
.join(NodeB, LeafB.parent)
# Filter for modified parents
.filter(NodeA.title != NodeB.title)
)
return query.all()
def find_orphaned_leaves(group_id_a, group_id_b):
"""
Leaves found in group A, but not in group B.
"""
LeafA = aliased(Leaf, name='leaf_a')
LeafB = aliased(Leaf, name='leaf_b')
query = (
session.query(LeafA)
.filter(~(
session.query(LeafB)
.filter(LeafA.id == LeafB.id)
.filter(group_id_b == LeafB.group_id)
.exists()
))
# Group membership
.filter(LeafA.group_id == group_id_a)
)
return query.all()
def find_deleted_leaves(group_id_a, group_id_b):
a_s = find_orphaned_leaves(group_id_a, group_id_b)
return tuple((a, None) for a in a_s)
def find_added_leaves(group_id_a, group_id_b):
b_s = find_orphaned_leaves(group_id_b, group_id_a)
return tuple((None, b) for b in b_s)
# add test data
_add_test_data()
# check the results
changed = find_changed_leaves(g1, g2)
assert 1 == len(changed)
le, ri = changed[0]
assert le.id == ri.id == l3
added = find_added_leaves(g1, g2)
assert 1 == len(added)
le, ri = added[0]
assert le is None
assert ri.id == l2
deleted = find_deleted_leaves(g1, g2)
assert 1 == len(deleted)
le, ri = deleted[0]
assert le.id == l1
assert ri is None