yii2 validate related models - yii2

We have 2 models:
Order
OrderItems
So when saving the related OrderItems I need to check some attributes from the parent Order.
Order has attributes: "time_begin" and "time_end" and OrderItem has its own "time_begin" and "time_end". So the rule is that times of the OrderItem must be between times of the parent Order.
For example, I have the following situation:
user changes the "time_end" of the Order to the bigger value and changes the "time_end" of OrderItem to bigger value too
try to save all the related data (Order and all its Items)
Order validations pass successfully
but when the OrderItem trying to validate its "time_end", it takes the old value of Order->time_end property and gives an validation error!!!
Any solution???
How to let OrderItem to know that Order has changed?

It's a complicated issue, you may consider this way:
- Don't call Order::validate() and OrderItems::validate() simultaneously.
- Order::validate() -> Order::save() first than OrderItems::validate() -> OrderItems::save() (is this failed, remember to revert Order if needed)

Related

How to get records with last dates in Django ORM(MySQL)?

I have models:
class Reference(models.Model):
name = models.CharField(max_length=50)
class Search(models.Model):
reference = models.ForeignKey(Reference)
update_time = models.DateTimeField(auto_now_add=True)
I have an instance of Reference and i need to get all last searches for the reference. Now i am doing it in this way:
record = Search.objects.filter(reference=reference)\
.aggregate(max_date=Max('update_time'))
if record:
update_time = record['max_date']
searches = reference.search_set.filter(update_time=self.update_time)
It is not a big deal to use 2 queries except the one but what if i need to get last searches for each reference on a page? I would have got 2x(count of references) queries and it would not be good.
I was trying to use this solution https://stackoverflow.com/a/9838438/293962 but it didn't work with filter by reference
You probably want to use the latest method.
From the docs, "Returns the latest object in the table, by date, using the field_name provided as the date field."
https://docs.djangoproject.com/en/1.8/ref/models/querysets/#latest
so your query would be
Search.objects.filter(reference=reference).latest('update_time')
I implemented a snippet from someone in gist but I don't remember the user neither have the link.
A bit of context:
I have a model named Medicion that contains the register of mensuration of a machine, machines are created in a model instance of Equipo, Medicion instances have besides of a Foreign key to Equipo, a foreign key to Odometro, this model serves as a kind of clock or metre, that's why when I want to retrieve data (measurements aka instances of Medicion model) for a certain machine, I need to indicate the clock as well, otherwise it would retrieve me a lot of messy and unreadable data.
Here is my implementation:
First I retrieve the last dates:
ult_fechas_reg = Medicion.objects.values('odometro').annotate(max_fecha=Max('fecha')).order_by()
Then I instance an Q object:
mega_statement = Q() # This works as 'AND' Sql Statement
Then looping in every date retrieved in the queryset(annotation) and establishing the Q statement:
for r in ult_fechas_reg:
mega_statement |= (Q(odometro__exact=r['odometro']) & Q(fecha=r['max_fecha']))
Finally passed this mega statement to the queryset that pursues to retrieve the last record of a model filtered by two fields:
resultados = Medicion.objects.filter(mega_query).filter(
equipo=equipo,
odometro__in=lista_odometros).order_by('odometro', 'fecha') # lista_odometros is a python list containing pks of another model, don't worry about it.

Yii2: ActiveQuery "with" not working

Circumstances
I have three models/db-tables related with 1:n each: An order has multiple commissions and a commission has multiple commission_positions. Therefore the commission_position has an FK-field containing a commission id. The commission itself has an FK-field containing the id of an order.
Order > Commission > CommissionPositions
Problem
What I need to do now is select all the CommissionPositions having a certain value in the related Order-Model. Obvious solution is to use the Query-Object of CommissionPosition which I extended with a named scope. The named scope looks like this:
class CommissionPositionQuery extends ActiveQuery
{
/**
* Named scope to filter positions of a certain alpha order id
* #param integer $id the alpha order id
* #return \common\models\query\CommissionPositionQuery
*/
public function alphaOrderId($id)
{
//TODO: with not working
$this->with(['commission.order']);
$this->andWhere(['alpha_order_id'=>$id]);
return $this;
}
}
The relation commission is defined on the Commission-Model and working. The second relation order is defined on the commission-model and working as well. The filtered field alpha_order_id is in the Order-Table. Now I execute the query like this:
$filteredPositions = CommissionPosition::find()->alphaOrderId(17)->all();
The scope is called successfully and the where-part is used, but when I check the generated SQL I see no join-statements even though I use the with-method to tell yii to fetch the relation together. The response is 'unknown column alpha_order_id' which makes sense as there is no join to the related tables. This is the generated SQL:
SELECT * FROM `commission_position` WHERE (`alpha_order_id`=17)
What am I missing? Is this a bug of Yii2?
Found the soution myself. The naming changes between Yii1 and Yii2 lead to a little confusion. To prevent others from wasting time here the details:
Yii1
In yii 1 you would join in a relation (exemplary: commission) directly like this:
$query->with = 'commission'
$query->together = true;
Yii2 / difference
When calling the with-method like showed in the question the relation was successfully added to the with-array of the ActiveQuery. However, when executing the query, the join part was missing.
Solution
Seems like the with-method is NOT the way to go. Instead I used the method called joinWith with the following signature:
public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')
Now as described in the answer I defined the relation as the first argument ('commission.order') and left the rest as is, because the default values are perfectly fine. Pay attention to the default value of the second parameter. this makes sure the relations are joined in directly!
VoilĂ ...the resulting sql contains the needed joins. One pitfall is to be considered though: Ambigious column namings is of course to be handled by ourselves! Link to the documentation of the method:
http://www.yiiframework.com/doc-2.0/yii-db-activequery.html#joinWith()-detail
If you want a JOIN use:
$this->joinWith(['commission.order']);

MDS business rule

I am new with MDS, and I have a question about one to many relation mapping in MDS
I have a product, contains descriptions in multiple languages. I have created two entities with derived hierarchy structure: product (P_ID, P_name)and Addtional description(P_ID, P_Name_in_German, P_name_in_English).
Additonal description is a drop down from product table from MDS UI, but I only want to populate info that releated with its same P_ID. How can I achieve that? Can I use business rules here and how it should look like?
(2012 Master data service' web interface)
I have also faced a similar problem. I have decided to add multiple fields for languages and apply business rules for them. Let's see how it would work for your entity:
Product entity
{
Code
Name
Name_in_English (text)
Name_in_German (text)
Default_language (domain based, points to Languages entity)
}
Languages entity
{
Code
Name
}
Add values to the Languages entity:
Code = "EN", Mane = "EN"
Code = "DE", name = "DE"
Now we need to add the following business rules to the Product entity:
BR1:
{
IF Condition - Equals: Default_language equals "EN"
THEN Action - Change value: Name = Name_in_English
}
BR2:
{
IF Condition - Equals: Default_language equals "DE"
THEN Action - Change value: Name = Name_in_German
}
After that You will see the product names in your product entity only in proper language which is chosen by drop-down field Default_language.
The second option:
If You want user to see only the Name field and don't want him to see additional fields,
You can hide those fields (Name_in_English and Name_in_German) by setting their width to zero.
Moreover, You can use attribute groups to separate two modes of view:
first mode (for the regular user) - You see only Code and Name
second mode (for the administrator) - You see fields: Name_in_English, Name_in_German, Default_language.
For it to work You need to create two attribute groups:
1) attribute group "EN" (add attributes Name and Code to the group)
2) attribute group "DE" (add attributes Name_in_English, Name_in_German, Default_language to the group)
Hope something of that is helpful!
You're missing something. You said you have a derived hierarchy structure but you don't show a domain based attribute on the Additional description entity with a pointer back to the Product entity. Perhaps you were thinking that the P_ID for Additional Description is the pointer back but it isn't (based on your explanation). It is the entity Code identifier for the Additional Description entity and not the key you need that points back to Product. Perhaps you meant One to One. One to Many implies you have a separate Parent_P_ID back to the Product entity.
Add the Domain Based Parent_P_ID attribute to the Additional Description entity and then reconstruct your derived hierarchy structure. This may not be that helpful because I think you have left something out of the explanation of what you are trying to do.
Hi Cocunuts,
We can not assign derived hierarchy to entities in MDS 2012,but we can achieve this in MDS 2016 , Your Domain based attribute(Drop down) cannot be further filtered in MDS 2012.

What is the best way to merge 2 tables with Active Record and Mysql

We need to allow users to customize their entities like products... so my intention was to have a product table and a custom_product table with just the information the users are allowed to change.
When a client goes to the product I want to merge the information, means I want to merge the two tables - the custom overwrites the default Products table.
I know that in mysql there exists a ifnull(a.title, b.title) way but I was wondering if there is any nice and efficient way to solve this in Rails 4 with Active Record. Assume that the products and custom products table have just 2 columns, ID and TITLE
I think you can convert both objects to JSON and then handle their params as a hash, using the merge method:
class Product
end
class Customization
belongs_to :product
end
a = Product.find(...)
b = a.customization
c = JSON(a.to_json).merge(JSON(b.to_json).reject!{|k,v| v.nil?})
Therefore c will contain all params from Product eventually overridden by those in Customization which are not nil.
If you still want to use a Product object with hybrid values (taken from Customization) you can try this:
a.attributes = a.attributes.merge(b.attributes.reject!{|k,v| v.nil?})
In this case a will still be a Product instance. I would recommend to keep the same attributes in both models when doing this.

How to do a MYSQL conditional select statement

Background
I'm faced with the following problem, relating to three tables
class_sectors table contains three categories of classes
classes table contains a list of classes students can attend
class_choices contains the first, second and third class choice of the student, for each sector. So for sector 1 Student_A has class_1 as first choihce, class_3 as second choice and class_10 as third choice for example, then for sector 2 he has another three choices, etc...
The class_choices table has these columns:
kp_choice_id | kf_personID | kf_sectorID | kf_classID | preference | assigned
I think the column names are self explanatory. preference is either 1, 2 or 3. And assigned is a boolean set to 1 once we have reviewed a student's choices and assigned them to a class.
Problem:
Writing an sql query that tells the students what class they are assigned to for each sector. If their class hasn't been assigned, it should default to show their first preference.
I have actually got this to work, but using two (very bloated??) sql queries as follows:
$choices = $db -> Q("SELECT
*, concat_ws(':', `kf_personID`, `kf_sectorID`) AS `concatids`
FROM
`class_choices`
WHERE
(`assigned` = '1')
GROUP BY
`concatids`
ORDER BY
`kf_personIDID` ASC,
`kf_sectorID` ASC;");
$choices2 = $db -> Q("SELECT
*, concat_ws(':', `kf_personID`, `kf_sectorID`) AS `concatids`
FROM
`class_choices`
WHERE
`preference` = '1'
GROUP BY
`concatids`
HAVING
`concatids` NOT IN (".iimplode($choices).")
ORDER BY
`kf_personID` ASC,
`kf_sectorID` ASC;");
if(is_array($choices2)){
$choices = array_merge($choices,$choices2);
}
Now $choices does have what I want.
But I'm sure there is a way to simplify this, merge the two SQL queries, and so it's a bit more lightweight.
Is there some kind of conditional SQL query that can do this???
Your solution uses two steps to enable you to filter the data as needed. Since you are generating a report, this is a pretty good approach even if it looks a bit more verbose than you might like.
The advantage of this approach is that it is much easier to debug and maintain, a big plus.
To improve the situation, you need to consider the data structure itself. When I look at the class_choices table, I see the following fields: kf_classID, preference, assigned which contain the key information.
For each class, the assigned field is either 0 (default) or 1 (when the class preference is assigned for the student). By default, the class with preference = 1 is the assigned one since you display it in the report when assigned=0 for all the student's class choices in a particular sector.
The data model could be improved by imposing a business rule as follows:
For preference=1 set the default value assigned=1. When the class selection process
takes place, and if the student gets assigned the 2nd or 3rd choice, then preference 1 is unassigned and the alternate choice assigned.
This means a bit more code in the application but it makes the reporting a bit easier.
The source of the difficulty is that the assignment process does not explicitly assign the 1st preference. It only updates assigned if the student cannot get the 1st choice.
In summary, your SQL is good and the improvements come from taking another look at the data model.
Hope this helps, and good luck with the work!