How to override mongoengine's QuerySet method? - python

How can I override a mongoengine's queryset method?
Specifically, I want to override .order_by(), but the closest I can get is to add another method ordered that would conditionally call .order_by():
class TransactionQuerySet(QuerySet):
def ordered(self, *args, target_data=None, **kwargs):
if target_data is None:
result = self.order_by(*args, **kwargs)
return result
else:
return 'transactions sorted by target data'
Ideally I would like this new method to be named the same as mongoengine's method - order_by - but if I do this I will exceed recursion depth when the queryset manager is called like Transaction.objects.order_by.
How can I do this?

To avoid recursion, you should call order_by method of the parent class. TransactionQuerySet should look like the following.
class TransactionQuerySet(QuerySet):
def order_by(self, *args, target_data=None):
if target_data is None:
return super().order_by(*args)
return "transactions sorted by target data"
Now if you call order_by on TransactionQuerySet object it won't fall in recursion.
class Transaction(Document):
title = StringField(max_length=100, required=True)
last_modified = DateTimeField(default=datetime.utcnow)
def __str__(self):
return self.title
meta = {"collection": "transaction_collection"}
with MongoClient("mongodb://localhost:27017") as client:
connection = client.get_database("transaction")
collection = connection.transaction_collection
transaction_set = TransactionQuerySet(Transaction, collection)
print(transaction_set.order_by("-title"))
OUTPUT
[<Transaction: ETHUSTD>, <Transaction: BTCUSTD>, ...]

Related

Python: Storing class type on a class variable durig class initialization

I'm trying to initialize an objects field with a class that needs to know the type that is using it:
class Device(Model):
objects = AbstractManager(Device)
# the rest of the class here
This is how AbstractManager is defined:
class AbstractManager:
def __init__(self, cls: type):
self.cls = cls
def all(self):
result = []
for cls in self._get_subclasses():
result.extend(list(cls.objects.all()))
return result
def _get_subclasses(self):
return self.cls.__subclasses__()
So I can later call this and returns all() from all subclasses:
Device.objects.all()
The issue here is that I cannot use Device while initializing Device.objects, since Device is still not initialized.
As a work-around I'm initializing this outside of the class, but there's gotta be a better way:
class Device(Model):
objects = None
# the rest of the class here
Device.objects = AbstractManager(Device)
PD: I have a C#/C++ background, so maybe I'm thinking too much about this in a static-typing mindset, can't tell
You don't need to add any additional logic for this. Django allows you to access model class from manager using self.model attribute:
def _get_subclasses(self):
return self.model.__subclasses__()
You do not have to do that. Django will automatically call the contribute_to_class method, where it will pass the model, and for a manager, it will be stored in self.model. You can thus simply implement this as:
from django.db.models.manager import ManagerDescriptor
class AbstractManager(models.Manager):
def all(self):
result = []
for cls in self._get_subclasses():
result.extend(list(cls.objects.all()))
return result
def contribute_to_class(self, model, name):
self.name = self.name or name
self.model = model
setattr(model, name, AbstractManagerDescriptor(self))
model._meta.add_manager(self)
def _get_subclasses(self):
return self.model.__subclasses__()
class AbstractManagerDescriptor(ManagerDescriptor):
def __get__(self, instance, cls=None):
if instance is not None:
raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__)
if cls._meta.swapped:
raise AttributeError(
"Manager isn't available; '%s.%s' has been swapped for '%s'" % (
cls._meta.app_label,
cls._meta.object_name,
cls._meta.swapped,
)
)
return cls._meta.managers_map[self.manager.name]
and add the manager as:
class Device(models.Model):
objects = AbstractManager()
That being said, I'm not sure that this is a good idea for two reasons:
you are returning a list, and normally .all() returns a QuerySet, you thus here "destroy" the laziness of the queryset, which can result in expensive querying; and
if one would use Device.objects.filter() for example, it would simply circumvent.
You might want to subclass the queryset, and then aim to implement that differently.

How to not return object reference from __init__

I'm new to ItemLoaders.
I've a set seen_ids in which I add all the product_ids which I scrape so that I can check if there is any duplicate and skip it at earliest.
The problem is, I want to do this in __init__. If it's a duplicate, I don't want any reference to be returned, and I can't explicitly return None from __init__. How would I do that?
seen_ids = set()
def __init__(self, item=None, selector=None, response=None, parent=None, product_id=None, **context):
if product_id in self.seen_ids:
return None
self.seen_ids.add(product_id)
super(GarmentLoader, self).__init__(item, selector, response, parent, **context)
item['retailer_sku'] = product_id
But it's giving error on None, and if I don't return anything, it returns object's reference and further checks fail.
It wouldn't work because constructor basically doesn't return anything else than instance and because instances wouldn't share seen_ids.
You can use class method instead:
class CustomItemLoader(ItemLoader):
seen_ids = set()
#classmethod
def with_product_id(cls, **kwargs):
product_id = kwargs.pop('product_id', None)
if product_id in cls.seen_ids:
return None
cls.seen_ids.add(product_id)
return cls(**kwargs)
Then create loader's instance using it:
loader = CustomItemLoader.with_product_id(response=response, product_id=product_id, ...)

Use Custom Filter with Django ModelChoice Filter

I am trying to create use the filter method with django-filters
See an example of the models below:
class Chicken(TimeStampedModel):
eggs = ForeignKey(Egg)
class Egg(TimeStampedModel):
hatched = BooleanField(default=False)
See an example of my current set up for the filter:
class ChickenFilter(FilterSet):
eggs__contains = ModelChoiceFilter(name="eggs", method='hatched_eggs', queryset=Eggs.objects.all())
def hatched_eggs(self, queryset, name, value):
print "We got eggs"
return queryset.filter(eggs__hatched=True)
The problem is that the method doesnt even print We got eggs when I hit the url. It just returns an empty queryset.
I did it like below:
In my urls I sent ?ids=1,2,3,4
class MyFilter(filters.FilterSet):
ids = django_filters.CharFilter(method='ids__in')
def ids__in(self, queryset, value, *args, **kwargs):
try:
if args:
ids = args[0].split(',')
ids = [int(_id) for _id in ids]
queryset = queryset.filter(id__in=ids)
except ValueError:
pass
return queryset
I was going through the same problem. My method was not being called.
So came to conclusion, I can make a workaround using:
Using custom methods in filter with django-rest-framework
class ChickenFilter(FilterSet):
eggs__contains = CharFilter(action='hatched_eggs')
def hatched_eggs(queryset, value):
print "We got eggs"
if value:
return queryset.filter(eggs__hatched=True)
return queryset

order is not working for structured property in app engine ndb

I have a model
from rated.lib import utility, data, money, search, kigo
class Property(ndb.Model):
created_at = data.UTCDateTimeProperty(auto_now_add=True)
updated_at = data.UTCDateTimeProperty(auto_now_add=True, auto_now=True, indexed=False)
users = ndb.KeyProperty(repeated=True)
user = ndb.KeyProperty()
rent_ = money.MoneyStructuredProperty(name='rent')
rent = money.structured_value(rent_)
but when i try to get the records order by rent using query -
properties = Property.query().order(Property.rent)
It gives me the error -
TypeError: order() expects a Property or query Order; received <super: <class 'structured_value'>, <structured_value object>>
structured_value is a class and it's syntax is --
class structured_value(object):
def __init__(self, field):
self.field = field
def __get__(self, instance, owner):
if instance is None:
return super(structured_value, self).__get__(instance, owner)
value = self.field.__get__(instance, owner)
if value is None:
return None
if value.cached is None:
value.cached = Money(value.value, value.currency)
print
return value.cached
def __set__(self, instance, value):
if value is None:
self.field.__set__(instance, value)
return
model_value = MoneyModel(
value=str(value.value),
currency=value.currency
)
model_value.cached = value
self.field.__set__(instance, model_value)
I am new to ndb, and wasted time to fix this thing......
any help will be appreciated
As the error suggests you are not trying to order by a property or Query order.
What does money.structured_value(rent_) call return? and how do you think it will work ?
Based on your code rent_ is what you should be ordering by, assuming your MoneyStructuredProperty is even orderable or ordered makes sense.
Ok, you have included the code for structured value and that is not how build a custom property, so of course you can't order by it.
Have a read up on building structured properties, for starters they have to inherit from ndb.Model.

Creating custom Field Lookups in Django

How do you create custom field lookups in Django?
When filtering querysets, django provides a set of lookups that you can use: __contains, __iexact, __in, and so forth. I want to be able to provide a new lookup for my manager, so for instance, someone could say:
twentysomethings = Person.objects.filter(age__within5=25)
and get back all the Person objects with an age between 20 and 30. Do I need to subclass the QuerySet or Manager class to do this? How would it be implemented?
A more flexible way to do this is to write a custom QuerySet as well as a custom manager. Working from ozan's code:
class PersonQuerySet(models.query.QuerySet):
def in_age_range(self, min, max):
return self.filter(age__gte=min, age__lt=max)
class PersonManager(models.Manager):
def get_query_set(self):
return PersonQuerySet(self.model)
def __getattr__(self, name):
return getattr(self.get_query_set(), name)
class Person(models.Model):
age = #...
objects = PersonManager()
This allows you to chain your custom query. So both these queries would be valid:
Person.objects.in_age_range(20,30)
Person.objects.exclude(somefield = some_value).in_age_range(20, 30)
Rather than creating a field lookup, best practice would be to create a manager method, that might look a little bit like this:
class PersonManger(models.Manager):
def in_age_range(self, min, max):
return self.filter(age__gte=min, age__lt=max)
class Person(models.Model):
age = #...
objects = PersonManager()
then usage would be like so:
twentysomethings = Person.objects.in_age_range(20, 30)
First, let me say that there is no Django machinery in place that's meant to publicly facilitate what you'd like.
(Edit - actually since Django 1.7 there is: https://docs.djangoproject.com/en/1.7/howto/custom-lookups/ )
That said, if you really want to accomplish this, subclass QuerySet and override the _filter_or_exclude() method. Then create a custom manager that only returns your custom QuerySet (or monkey-patch Django's QuerySet, yuck). We do this in neo4django to reuse as much of the Django ORM queryset code as possible while building Neo4j-specific Query objects.
Try something (roughly) like this, adapted from Zach's answer. I've left actual error handling for the field lookup parsing as an exercise for the reader :)
class PersonQuerySet(models.query.QuerySet):
def _filter_or_exclude(self, negate, *args, **kwargs):
cust_lookups = filter(lambda s: s[0].endswith('__within5'), kwargs.items())
for lookup in cust_lookups:
kwargs.pop(lookup[0])
lookup_prefix = lookup[0].rsplit('__',1)[0]
kwargs.update({lookup_prefix + '__gte':lookup[1]-5,
lookup_prefix + '__lt':lookup[1]+5})
return super(PersonQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)
class PersonManager(models.Manager):
def get_query_set(self):
return PersonQuerySet(self.model)
class Person(models.Model):
age = #...
objects = PersonManager()
Final remarks - clearly, if you want to chain custom field lookups, this is going to get pretty hairy. Also, I'd normally write this a bit more functionally and use itertools for performance, but thought it was more clear to leave it out. Have fun!
As of Django 1.7, there is a simple way to implement it. Your example is actually very similar to the one from the documentation:
from django.db.models import Lookup
class AbsoluteValueLessThan(Lookup):
lookup_name = 'lt'
def as_sql(self, qn, connection):
lhs, lhs_params = qn.compile(self.lhs.lhs)
rhs, rhs_params = self.process_rhs(qn, connection)
params = lhs_params + rhs_params + lhs_params + rhs_params
return '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), params
AbsoluteValue.register_lookup(AbsoluteValueLessThan)
While registering, you can just use Field.register_lookup(AbsoluteValueLessThan) instead.

Categories