Django model operating on a queryset - python

I'm new to Django and somewhat to Python as well. I'm trying to find the idiomatic way to loop over a queryset and set a variable on each model. Basically my model depends on a value from an api, and a model method must multiply one of it's attribs by this api value to get an up-to-date correct value.
At the moment I am doing it in the view and it works, but I'm not sure it's the correct way to achieve what I want. I have to replicate this looping elsewhere.
Is there a way I can encapsulate the looping logic into a queryset method so it can be used in multiple places?
NOTE: the variable I am setting on each model instance is just a regular attribute, not saved to db. I just need to be able to set that variable, not save it.
I have this atm (I am using django-rest-framework):
class FooViewSet(viewsets.ModelViewSet):
model = Foo
serializer_class = FooSerializer
bar = # some call to an api
def get_queryset(self):
# Dynamically set the bar variable on each instance!
foos = Foo.objects.filter(baz__pk=1).order_by('date')
for item in foos:
item.needs_bar = self.bar
return items
I would think something like so would be better:
def get_queryset(self):
bar = # some call to an api
# Dynamically set the bar variable on each instance!
return Foo.objects.filter(baz__pk=1).order_by('date').set_bar(bar)
I'm thinking the api hit should be in the controller and then injected to instances of the model, but I'm not sure how you do this. I've been looking around querysets and managers but still can't figure it out nor decided if it's the best method to achieve what I want.
Can anyone suggest the correct way to model this with django?
Thanks.

You can set some new properties on queryset items, but they will not update database (will be saved just in local namespace). I suppose that you want to recalculate field of your model multiplying it by some value:
class Foo(models.Model):
calculated_field = models.BigIntegerField(default=0)
def save(self, *args, **kwargs):
if self.pk is not None: # it's not a new record
foo = kwargs.get('foo')
if foo:
self.calculated_field = self.calculated_field * int(foo)
super(Foo, self).save(*args, **kwargs) # Call the "real" save() method.
def get_queryset(self):
bar = # some call to an api
# Dynamically set the bar variable on each instance!
foos = Foo.objects.filter(baz__pk=1).order_by('date')
for item in foos:
item.save(foo=bar)
# return updated data
return Foo.objects.filter(baz__pk=1).order_by('date')
At some point you might need to use transactions if you will run this code simultaneously.

Related

Django: How to Set Default Field Value By Model Method

I have a simple Model. I want to calculate a default value for one of its fields (let's call it score) based on some of the fields of the same model:
My ideal way to do it is as so:
class ModelA(models.model):
field_1 = models.IntegerField()
field_1 = models.IntegerField()
score = models.IntegerField(default=self.calculate_default())
def calculate_default(self):
default = (self.field_1 + self.field_2) / 2
return default
Problem: the calculate_default method does not have access to self.
One solution I tried was overriding the save() method. The problem is that it prevents the user to further change the score field.
Another method I searched was to override the inti of the class. But according to django it might have consequences if not implemented correctly.
What is the cleanest way to achieve this? How can I set the default of a field to be calculated from other fields of the same model such that it could be later changed by the user?
I have a hunch that classmethod is the way to go but I haven't been able to pull it off yet.
the calculate_default method does not have access to self. One solution I tried was overriding the save() method. The problem is that it prevents the user to further change the score field.
Actually its the right approach. But you can put a check in save() method if the value of score is empty or not:
def save(self, *args, **kwargs):
if self.score == None:
self.score = self.calculate_default()
super().save(*args, **kwargs)
FYI, in classmethod, you can't access self or the ModelA object. If the class method you intend to use is something not dependent on the current instance of ModelA's fields or totally independent value(for example: current date datetime.now), then you can use that.

Django - How to return all objects in a QuerySet in their subclass form, rather than their parent class form?

I have a class Set with a many-to-many relationship to Item. I have lots of 'set' objects all containing lots of 'items'.
However, the Item class has been subclassed to create Article, Podcast, Video, and Episode. Basically, everything on the database was originally an Item. If my_set is a Set instance, containing Items - how do I create a Queryset which returns those objects in their subclass form? Ie, rather than me getting a Queryset full of Item instances, I get a Queryset with Article, Episode, Video, Podcast instances.
How would I get `my_set.objects.all().as_subclass()' to work?
class Item(models.Model, AdminVideoMixin):
base_attributes = 'foo'
def as_episode(self):
return Episode.objects.get(slug=self.slug)
class Video(Item):
class specific fields
class Article(Item):
class specific fields
class Podcast(Item):
class specific fields
class Episode(Item):
class specific fields
class Set(Item):
front_page = models.BooleanField(max_length=300, blank=False, default=False, null=False)
items = models.ManyToManyField(Item, related_name='in_sets', through='SetMeta', max_length=5000,)
def get_absolute_url(self):
return reverse('foo')
def ordered(self):
return self.items.all().order_by('-itemOrder__order')
def episodes(self):
episode_list = []
for each_item in self.items.all():
episode_list.append(each_item.as_episode())
return episode_list
def __str__(self):
return self.title
As you can see I tried two methods - to write a model method on Item() which returns itself as an Episode - but this only worked for single instances rather than a Queryset. As such I wrote a method on Set which can perform that method on all items within the self, but this doesn't produce a Queryset, just a list, and it feels messy?
Update: have just skimmed the django-polymorphic documentation again, and it seems to be exactly what you want. So the rest of my answer is probably not very useful, unless you are prohibited from taking code out of django-packages
I don't think Django provides a way to express a queryset that returns objects of more than one model type. Querysets are supposed to map inro SQL queries, and I don't think SQL can return rows from more than one table mixed up. (I'm not an SQL expert, so I may be wrong). However, if you don't want a list, Python provides a means to take a queryset and apply a transformation to each Item instance it returns: a generator function. So, for example, you could code
def items_as_subclasses(qs):
for instance in qs:
try:
yield instance.video
continue
except Video.DoesNotExist:
pass
try:
yield instance.article
continue
except Article.DoesNotExist:
pass
try: ...
raise ProbableCodingError(
f"Item instance id={instance.id} does not have a known subclass"
)
and then write
for item_subclass_instance in items_as_subclasses(queryset):
# whatever
or indeed pass "items":items_as_subclasses( queryset) into a template rendering
context.
If there is a long list of possible subclasses it might be better to have a subclass_type field in the base class, and use that to go straight to the only valid subclass field.
There's a nullable OneToOne link from the base class to its particular subclass, so you can write querysets that interrogate subclasses.
Or you could investigate django-polymorphic, which I once skimmed, and which I vaguely remember is for this sort of usage.

Bulk update using model's method in SQLAlchemy

I'm developing an application with SQLAlchemy and I've run into a bit of an issue. I would like to run a method on all models returned by a query and make all that in a single SQL query, while preserving the readability the ORM offers.
The method in question is very simple and doesn't depend on any external data nor makes any more queries. It's also fine if all the models in the bulk update use the same exact value, so the value itself needs to be evaluated only once.
Here's my model:
class Item(db.Model):
last_fetch = db.Column(db.DateTime)
def refresh(self):
self.last_fetch = datetime.utcnow()
I would like to call the refresh() function on all models returned by a query - for the sake of example let's assume it's Item.query.all().
I can iterate through them and run the method on each model but that would run a separate query for each one of them:
items = Item.query.all()
for item in items:
item.refresh()
Or I could do the following which works however I've now moved my refresh() logic from the model to the code that would otherwise just call that method:
items = Item.query.all()
items.update({Item.last_fetch: datetime.utcnow()})
Is there a better solution? A way to define a "smart" method on the model that would somehow allow the ORM to run it in bulk while still keeping it a model method?
Regards.
Since SQLAlchemy doesn't recreate queried objects using __init__ but __new__.
You could either override __new__ on your model or try and see if the #orm.reconstructor decorator explained in Constructors and Object Initialization would work.
Using the reconstructor:
class Item(db.Model):
last_fetch = db.Column(db.DateTime)
#orm.reconstructor
def set_last_fetch_on_load(self):
self.last_fetch = datetime.datetime.now()
Overriding __new__:
class Item(db.Model):
last_fetch = db.Column(db.DateTime)
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls, *args, **kwargs)
obj.last_fetch = datetime.datetime.now()
return obj
Note: Haven't tested it.

Storing related model references in Django?

I'm implementing a bare-bones history tracking mechanism for my Django app, in which the models I care to track override the save() and delete() methods. In each method, I create my history objects as necessary:
def save(self, *args, **kwargs):
super(MyModel, self).save(*args, **kwargs)
# Create the historical model based on what we were given
h = Historical_MyModel(**{field.attname: getattr(self, field.attname) for field in self._meta.fields})
# Set some other fields as necessary...
h.save()
Since the code for each save() and delete() method is similar, I figured a good way to prevent typing the same code is to create an abstract base class to have the similar code in one place. One thing I'm struggling with, however, is how to handle creating the Historical_{Model} instance for each child class (each Historical_{Model} is essentially a copy of the original model, with additional info like who made the change, when the change occurred, etc.).
In my base class, the method would look something like this, I think:
class HistoryTrackedModel(models.Model):
def save(self, *args, **kwargs):
super(self.model, self).save(*args, **kwargs)
# Create the historical model based on what we were given
h = SOME_HISTORICAL_MODEL_INSTANCE(**{field.attname: getattr(self, field.attname) for field in self._meta.fields})
# Other fields get set ...
h.save()
class Meta:
abstract = True
The SOME_HISTORICAL_MODEL_INSTANCE bit above is the piece I'm stuck on. How can I get the associated historical model for a specific model I'm tracking? Is there an easy way to store a reference to it in each child class? I'd like to prevent code duplication, and I thought this was the right avenue, but I'm stuck on this one point. Any help would be appreciated.
I think the most straightforward way would be to store the value as a class attribute:
class HistoricalFoo(models.Model):
...
class Foo(HistoryTrackedModel):
history_model = HistoricalFoo
....
class HistoryTrackedModel(models.Model):
def save(self):
...
h = self.history_model(...)
An alternative would be to generate the historical model names programmatically:
class HistoryTrackedModel(models.Model):
def save(self):
...
history_model = globals()["Historical" + self.__class__.__name__]
h = history_model(...)

Extending appengine's db.Property with caching

I'm looking to implement a property class for appengine, very similar to the existing db.ReferenceProperty. I am implementing my own version because I want some other default return values. My question is, how do I make the property remember its returned value, so that the datastore query is only performed the first time the property is fetched? What I had is below, and it does not work. I read that the Property classes do not belong to the instances, but to the model definition, so I guess that the return value is not cached for each instance, but overwritten on the model every time. Where should I store this _resolved variable?
class PageProperty(db.Property):
data_type = Page
def get_value_for_datastore(self, model_instance):
page = super(PageProperty, self).get_value_for_datastore(model_instance)
self._resolved = page
return page.key().name()
def make_value_from_datastore(self, value):
if not hasattr(self, '_resolved'):
self._resolved = Page.get_by_name(value)
return self._resolved
Edit
Alex' answer is certainly usable. But it seems that the built-in db.ReferenceProperty does store the _RESOLVED variable on the model instance. As evidenced by:
[...]
setattr(model_instance, self.__resolved_attr_name(), value)
[...]
def __resolved_attr_name(self):
return '_RESOLVED' + self._attr_name()
The get_value_for_datastore method is passed the model instance, but make_value_from_datastore is not, so how do they find the _RESOLVED property from that method?
Edit 2
From the code I gather that google is using the __get__() and __set__() methods, both of which do get the model instance as an argument. Are those usable in custom classes? What is the difference with get_value_for_datastore and its counterpart?
A PageProperty instance exists per-model, not per-entity (where an entity is an instance of the model class). So I think you need a dictionary that maps pagename -> Page entity, instead of a single attribute per PageProperty instance. E.g., maybe something like...:
class PageProperty(db.Property):
data_type = Page
def __init__(self, *a, **k):
super(PageProperty, self).__init__(*a, **k)
self._mycache = {}
def get_value_for_datastore(self, model_instance):
page = super(PageProperty, self).get_value_for_datastore(model_instance)
name = page.key().name()
self._mycache[name] = page
return name
def make_value_from_datastore(self, value):
if value not in self._mycache:
self._mycache[value] = Page.get_by_name(value)
return self._mycache[value]
If you only want to change some small part of the behaviour of ReferenceProperty, you may want to simply extend it, overriding its default_value method. You may find the source for ReferenceProperty to be instructive.

Categories