Valuate instances of Django Models.
Use case example from Hedde van der Heide on Vimeo.
Adds a '_networth' and '_relative_networth' field to your model
which hold information about the instance's 'total value'.
note: relative networth is measured against the highest valued
current instance. The're two signals to hook into for ceiling
increases or decreases that can be used to recalculate every
object.
note: (currently) does not support floats, nor supports negative
values (i.e. penalties)
$ pip install django-networth
1. Add 'networth' to your INSTALLED_APPS
2. Optionally change the default networth setting NETWORTH_DEFAULT
to a POSITIVE interger (defaults to 1)
3. Make your model(s) inherit NetworthModel (see below)
4. Create networth rules (see below)
5. Run a schemamigration / migrate cycle
note: make sure you inherit the NetworthManager also if you're not
using a default manager for your model(s)
class Object(NetworthModel):
first_name = models.CharField(max_length=25)
last_name = models.CharField(max_length=75, blank=True, null=True)
tags = TaggableManager(through=TaggedItem, blank=True)
other_tags = TaggableManager(through=OtherTaggedItem, blank=True)
class Networth:
fields = (
('first_name', (True, 1)),
('last_name', (lambda f: f.startswith('P'), 5)),
('tags', (lambda f: f.count(), 'result')),
('other_tags', (lambda f: f.count(), lambda result: result * 2))
)
So,
1. Networth.fields holds a tuple with tuples
2. The first value of the tuple has to be the field's name
3. The second argument must be a tuple with two parts
fields = (
(field_name, (condition, award)),
)
a. the condition;
can be any callable or non callable (e.g. a boolean).
if callable, receives the instance as its first argument.
b. the award;
can be any callable or integer, but MUST return an
integer.
if the condition is met, this represents the field's networth
return value.
if callable, receives the condition's result as its first
argument.
note: 'result' is a reserved keyword which will simply return the
output of the condition.
note: it's perfectly legit to declare the same field twice.
Consider the following pseudo instances (first_name, last_name, tags, other_tags,):
('Pete', None, None, None).networth()
>>> 1
('Pete', 'James', None, None).networth()
>>> 1
('Pete', 'Philly', None, None).networth()
>>> 1
('Pete', 'Philly', None, None).networth(realtime=True, commit=True) # commit to db (self._networth)
>>> 6
The penultimate example returned 1 because by default networth() and relative_networth() return the database value, to force the realtime value use networth(realtime=True) and relative_networth(realtime=True)
('Pete', 'Philly', None, None)._networth # test commit
>>> 6
('Pete', 'Philly', <TagManager (1 tag)>, None).networth(realtime=True, commit=True)
>>> 7
('Pete', 'Philly', <TagManager (1 tag)>, <OtherTagManager (1 tag)>).networth(realtime=True)
>>> 9
('Pete', 'Philly', <TagManager (1 tag)>, <OtherTagManager (1 tag)>)._networth # I didn't commit !
>>> 7
In the penultimate example 'result' defines to use the outcome of the function itself as the net result, the last example defines a callable, which in this case is used to multiply the result by a factor 2.
('Pete', 'Philly', <TagManager (1 tag)>, <OtherTagManager (1 tag)>).relative_networth(realtime=True, commit=True)
>>> 100
('Pete', 'Philly', <TagManager (1 tag)>, <OtherTagManager (1 tag)>)._relative_networth # test commit
>>> 100
Relative networth calculates the percentage of the current object's networth compared to the highest valued object known. This can be useful when calculating profile completeness for example.
from networth.models import NetworthModel as BaseNetworthModel
class NetworthModel(BaseNetworthModel):
def networth(self, realtime=False, commit=False):
return super(NetworthModel, self).networth(realtime=realtime, commit=commit)
# models.py
from celery import current_app
from celery.contrib.methods import task_method
from networth.models import NetworthModel
class Pizza(NetworthModel):
name = models.CharField(max_length=140, blank=True, null=True)
toppings = models.ForeignKey('takeaway.Topping', null=True)
class Meta:
verbose_name = _(u"Pizza")
verbose_name_plural = _(u"Pizze")
class Networth:
fields = (
('name', (True, 0)),
('toppings', (lambda m: m.count(), 'result'))
)
@current_app.task(filter=task_method)
def networth(self, realtime=False, commit=False):
return super(Pizza, self).networth(realtime=realtime, commit=commit)
# views.py
class PizzaDetailView(DetailView):
queryset = Pizza.objects.all()
def render_to_response(self, context, **response_kwargs):
self.object.networth.delay(realtime=True, commit=True)
return super(PizzaDetailView, self).render_to_response(context, **response_kwargs)
# pizza_detail.html
<div>{{ object.networth }}</div>