Is there a way to have shorter `_cls` values in mongoengine? - optimization

Is there a way to have shorter _cls values in mongoengine, apart from making the names of the classes shorter (which would make code difficult to read)?
I was looking for something like this:
class User(Document):
login = StringField(primary_key = True)
full_name = StringField()
meta = { "short_class_name": "u" }
class StackOverFlowUser(User):
rep = IntField()
meta = { "short_class_name": "s" }
If the short_class_name meta attribute existed (but I have not found it or anything similar), then we could have this:
{ "_cls" : "s", "_id" : "john",
"full_name" : "John Smith", "rep" : 600 }
instead of this:
{ "_cls" : "User.StackOverFlowUser", "_id" : "john",
"full_name" : "John Smith", "rep" : 600 }
In this example, this leads to about 20% space saving, and in some cases, it could be even greater.
I guess mongoengine is open source, I could go ahead and code this, but if you know a simpler solution, I would love to hear it.
Thanks.

After looked into mongoengine's source code I and in most part MiniQuark got next hack:
def hack_document_cls_name(cls, new_name):
cls._class_name = new_name
from mongoengine.base import _document_registry
_document_registry[new_name] = cls
or as class decorator:
def hack_document_cls_name(new_name):
def wrapper(cls):
cls._class_name = new_name
from mongoengine.base import _document_registry
_document_registry[new_name] = cls
return cls
return wrapper
We see no other way than hacking with the _class_name and the _document_registry.
When you want to rename a class, you must apply this hack immediately after the class definition (or at least before you define any sub-classes, or else they will have a _types attribute with the base class's long name). For example:
class User(Document):
login = StringField(primary_key = True)
full_name = StringField()
hack_document_cls_name(User, "u")
class StackOverflowUser(User):
rep = IntField()
hack_document_cls_name(StackOverflowUser, "s")
or as class decorator:
#hack_document_cls_name("u")
class User(Document):
login = StringField(primary_key = True)
full_name = StringField()
#hack_document_cls_name("s")
class StackOverflowUser(User):
rep = IntField()

Ok, so far, the best I could come up with is this. It works, but I'm sure there must be less hackish solutions...
class U(Document): # User
meta = { "collection": "user" }
login = StringField(primary_key = True)
full_name = StringField()
class S(U): # StackOverflowUser
rep = IntField()
User = U; del(U)
StackOverflowUser = S; del(S)
User.__name__ = "User"
StackOverflowUser.__name__ = "StackOverflowUser"
Now when I do this:
StackOverflowUser.objects.create(login="john", full_name="John Smith", rep=600)
I get this document in the user collection:
{ "_cls" : "U.S", "_id" : "john", "full_name" : "John Smith", "rep" : 600 }
Compared to the standard behavior, this is about 20% space saving. But I don't like how hackish it is.

Related

Django Nested Annotate

I have 3 models
class QuestionsModel(models.Model):
created = models.DateTimeField(auto_now_add=True)
Question = models.CharField(max_length=200)
class AnswersModel(models.Model):
Question = models.ForeignKey(QuestionsModel, related_name='QuestionAnswer')
Answer = models.CharField(max_length=200)
class UsersAnswerModel(models.Model):
Answer = models.ForeignKey(AnswersModel, related_name='UsersAnswer')
RegistrationID = models.CharField(max_length=200)
I am trying to Count How many UsersAnswer the Question
what I tried :
class DashboardAdmin(admin.ModelAdmin):
class Meta:
model = QuestionsModel
change_list_template = 'admin/Dashboard_change_list.html'
date_hierarchy = 'created'
def has_add_permission(self, request):
return False
def changelist_view(self, request, extra_context=None):
response = super().changelist_view(
request,
extra_context=extra_context,
)
try:
qs = response.context_data['cl'].queryset
except (AttributeError, KeyError):
return response
metrics = {
'totalAnswers' : models.Count('QuestionAnswer'),
'totalUsersAnswer' : models.Count(UsersAnswer=OuterRef('QuestionAnswer'))
}
response.context_data['summary'] = list(
qs
.values('Question')
.annotate(**metrics)
.order_by('-totalUsersAnswer')
)
return response
admin.site.register(DashboardModel, DashboardAdmin)
I could not solve
'totalUsersAnswer' : models.Count(UsersAnswer=OuterRef('QuestionAnswer'))
how to count nested foreign key
any help please
class AnswersModel(models.Model):
Question = models.ForeignKey(QuestionsModel, related_name='QuestionAnswer')
Answer = models.CharField(max_length=200)
class UsersAnswerModel(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.PROTECT)
Answer = models.ForeignKey(AnswersModel, related_name='UsersAnswer')
RegistrationID = models.CharField(max_length=200)
Q- How many users answer a Question?
e.g
user_answer = UsersAnswerModel.objects.filter(Answer__question__id=2).count()
You can now annotate based on your need.
Your models need to be renamed
AnswersModel to simply Answer
UsersAnswerModel to UserAnswer and there should be a user entity as a field as I have done above.
3.Fieldnames in model is better lowercased.
I have Solve it by add QuestionAnswer__UsersAnswer
metrics = {
'totalAnswers' : models.Count('QuestionAnswer', distinct=True),
'totalUsersAnswer' : models.Count('QuestionAnswer__UsersAnswer'),
}

Convert SQL to Active Record Query matching on IN

How would I convert this sort of SQL into Active Record Syntax.
I've struggled mainly resolving the IN with the other elements.
SELECT \"accounts\".* FROM account_categories, accounts WHERE \"accounts\".\"worksheet_id\" = 5 AND (account_categories.name IN ('Savings','Deposit') AND account_categories.id = accounts.account_category_id) ORDER BY \"accounts\".\"id\" ASC"
worksheet_id will vary, won't always be 5.
I want to use this in a scope in the Account model.
Similar like this
scope :savings, -> { from('account_categories, accounts').where("account_categories.name = ? AND account_categories.id = zen_accounts.account_category_id", 'Savings') }
but testing for both Savings & Deposit something like this:
scope :savings_and_deposit, -> { from('account_categories, accounts').where("account_categories.name = ? AND account_categories.id = zen_accounts.account_category_id", ['Savings','Deposit]) }
Try this code:
scope :savings, -> (worksheet_id) { filtered_categories(worksheet_id, ['Savings']) }
scope :savings_and_deposit, -> (worksheet_id) { filtered_categories(worksheet_id, ['Savings', 'Deposit']) }
scope :filtered_categories, -> (worksheet_id, category_names) do
joins(:account_categories).
where(worksheet_id: worksheet_id).
where(account_categories: {name: category_names}).
order(id: :asc)
end
This code supposes what Account model already has relation account_categories, otherwise replace joins(:account_categories) with joins("JOIN account_categories ON account_categories.id = accounts.account_category_id")

get count from related table data in django rest framework

I have two tables
class student(models.Model):
frist_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
class subject(models.Model):
student = models.ForeignKey(student)
sub_name = models.CharField()
I want student list and subject count in serializer
my serializer
classs SubjectSerializers(serializers.ModelSerializer):
class Meta:
model = JobPosting
fields = ('id','sub_name')
class StudentSerializers(serializers.ModelSerializer):
sub = SubjectSerializers(source = 'student')
class Meta:
model = JobPosting
fields = ('id','first_name', 'last_name','sub')
How can i get subject count for every student in serializer, Now i am geting subject table data but i want count like this
"detail": [{
"id": 680,
"first_name": "riya",
"last_name": "tri",
"subject_count": 5
}],
You can achieve this by using a serializer method field
Then StudentSerializer becomes the following:
class StudentSerializers(serializers.ModelSerializer):
sub = SubjectSerializers(source = 'student')
subject_count = serializers.SerializerMethodField()
class Meta:
model = JobPosting
fields = ('id','first_name', 'last_name','sub')
def get_subject_count(self, student):
return Subject.objects.filter(student=student).count()

Custom data in one2many field in Odoo

I am working on this Odoo assignment. I have to make a custom module in which the requirement is like this.
There is a form say "Notebook" it contains a field that comes from 'hr.employee' i.e. Many2one. Next thing this form will contain is a table which 3 columns (Quality, Score, Comment). Now Qualities must be a master table which contains many qualities name.
I have achieved this task in the way SalesOrder form works i.e. for a particular sales order there are multiple sales lines.
But I want all the qualities to be there on form with default score value of 0.
Here is the code
Please tell me the resolution
class qualities_fields(models.Model):
_name = "ayda.qualities.fields"
_description = "Contains only Attributes"
#api.multi
def name_get(self):
data = []
for rows in self:
value = ''
value += rows.quality_name
data.append((rows.id, value))
return data
quality_name = fields.Char(string="Quality Name")
class qualities_data(models.Model):
_name = "qualities.data"
_description = "All points mandatory to be filled"
quality_id = fields.Many2one('notebook', string="Quality IDs")
quality_name = fields.Many2one('qualities.fields', string="Quality Name")
score = fields.Integer(string="Score")
comment = fields.Char(string="Comment")
class notebook(models.Model):
_name = "notebook"
_description = "Checking one2many of qualities"
def createRecords(self):
cr = self.pool.cursor()
quantity_fields = self.pool.get('qualities.fields').search(cr, self.env.uid, [])
quantity_lines = []
for field in quantity_fields:
quality_data = {
'quality_id' : self.id,
'quality_name' : field.id,
'score' : 0,
'comment' : ''
}
quantity_lines.append(quality_data)
return quantity_lines
name = fields.Many2one('hr.employee', string="Name")
qualities_line = fields.One2many('qualities.data', 'quality_id', string="Qualities Line", default=createRecords)
There's an easier way to do this, on the field definition just set the default score to 0
score = fields.Integer(string="Score", default=0)
That way all scores would be zero when they're created

Grails query to filter on association and only return matching entities

I have the following 1 - M (one way) relationship:
Customer (1) -> (M) Address
I am trying to filter the addresses for a specific customer that contain certain text e.g.
def results = Customer.withCriteria {
eq "id", 995L
addresses {
ilike 'description', '%text%'
}
}
The problem is that this returns the Customer and when I in turn access the "addresses" it gives me the full list of addresses rather than the filtered list of addresses.
It's not possible for me to use Address.withCriteria as I can't access the association table from the criteria query.
I'm hoping to avoid reverting to a raw SQL query as this would mean not being able to use a lot functionality that's in place to build up criteria queries in a flexible and reusable manner.
Would love to hear any thoughts ...
I believe the reason for the different behavior in 2.1 is documented here
Specifically this point:
The previous default of LEFT JOIN for criteria queries across associations is now INNER JOIN.
IIRC, Hibernate doesn't eagerly load associations when you use an inner join.
Looks like you can use createAlias to specify an outer join example here:
My experience with this particular issue is from experience with NHibernate, so I can't really shed more light on getting it working correctly than that. I'll happily delete this answer if it turns out to be incorrect.
Try this:
def results = Customer.createCriteria().listDistinct() {
eq('id', 995L)
addresses {
ilike('description', '%Z%')
}
}
This gives you the Customer object that has the correct id and any matching addresses, and only those addresses than match.
You could also use this query (slightly modified) to get all customers that have a matching address:
def results = Customer.createCriteria().listDistinct() {
addresses {
ilike('description', '%Z%')
}
}
results.each {c->
println "Customer " + c.name
c.addresses.each {address->
println "Address " + address.description
}
}
EDIT
Here are the domain classes and the way I added the addresses:
class Customer {
String name
static hasMany = [addresses: PostalAddress]
static constraints = {
}
}
class PostalAddress {
String description
static belongsTo = [customer: Customer]
static constraints = {
}
}
//added via Bootstrap for testing
def init = { servletContext ->
def custA = new Customer(name: 'A').save(failOnError: true)
def custB = new Customer(name: 'B').save(failOnError: true)
def custC = new Customer(name: 'C').save(failOnError: true)
def add1 = new PostalAddress(description: 'Z1', customer: custA).save(failOnError: true)
def add2 = new PostalAddress(description: 'Z2', customer: custA).save(failOnError: true)
def add3 = new PostalAddress(description: 'Z3', customer: custA).save(failOnError: true)
def add4 = new PostalAddress(description: 'W4', customer: custA).save(failOnError: true)
def add5 = new PostalAddress(description: 'W5', customer: custA).save(failOnError: true)
def add6 = new PostalAddress(description: 'W6', customer: custA).save(failOnError: true)
}
When I run this I get the following output:
Customer A
Address Z3
Address Z1
Address Z2