I want to create a model, which is symmetric on two fields. Let's call the model Balance:
class Balance (models.Model):
payer = models.ForeignKey(auth.User, ...)
payee = models.ForeignKey(auth.User, ...)
amount = models.DecimalField(...)
It should have the following property:
balance_forward = Balance.objects.get(payer=USER_1, payee=USER_2)
balance_backward = Balance.objects.get(payer=USER_2, payee=USER_1)
balance_forward.amount == -1 * balance_backward.amount
What is the best way to implement this?
You can aggregate on the Balance objects with:
from django.db.models import Case, F, Sum, When
from django.conf import settings
class Balance(models.Model):
payer = models.ForeignKey(settings.AUTH_USER_MODEL, …)
payee = models.ForeignKey(settings.AUTH_USER_MODEL, …)
amount = models.DecimalField(…)
def get_balance(cls, payer, payee):
return cls.objects.filter(
Q(payer=payer, payee=payee) | Q(payer=payee, payee=payer)
).aggregate(
total=Sum(
Case(
When(payer=payer, then=F('amount')),
otherwise=-F('amount'),
output_field=DecimalField(…),
)
)
)['total']
This will look for all Balances between the payer and the payee, and subtract the ones in the opposite direction. The Balance.get_balance(payer=foo, payee=bar) will thus determine the total flow from foo to bar.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
So, I came up with the following solution. Feel free to suggest other solutions.
class SymmetricPlayersQuerySet (models.query.QuerySet):
def do_swap(self, obj):
obj.payer, obj.payee = obj.payee, obj.payer
obj.amount *= -1
def get(self, **kwargs):
swap = False
if "payer" in kwargs and "payee" in kwargs:
if kwargs["payer"].id > kwargs["payee"].id:
swap = True
kwargs["payer"], kwargs["payee"] = \
kwargs["payee"], kwargs["payer"]
obj = super().get(**kwargs)
if swap:
self.do_swap(obj)
return obj
def filter(self, *args, **kwargs):
if (
("payer" in kwargs and "payee" not in kwargs) or
("payee" in kwargs and "payer" not in kwargs)
):
if "payee" in kwargs:
key, other = "payee", "payer"
else:
key, other = "payer", "payee"
constraints = (
models.Q(payer=kwargs[key]) |
models.Q(payee=kwargs[key])
)
queryset = super().filter(constraints)
for obj in queryset:
if getattr(obj, other) == kwargs[key]:
self.do_swap(obj)
return queryset
return super().filter(*args, **kwargs)
class BalanceManager (models.Manager.from_queryset(SymmetricPlayersQuerySet)):
pass
class Balance (models.Model):
objects = BalanceManager()
payer = models.ForeignKey(
Player,
on_delete=models.CASCADE,
related_name='balance_payer',
)
payee = models.ForeignKey(
Player,
on_delete=models.CASCADE,
related_name='balance_payee',
)
amount = models.DecimalField(decimal_places=2, max_digits=1000, default=0)
def do_swap(self):
self.payer, self.payee = self.payee, self.payer
self.amount *= -1
def save(self, *args, **kwargs):
swap = False
if self.payer.id > self.payee.id:
swap = True
self.do_swap()
result = super().save(*args, **kwargs)
if swap:
self.do_swap()
return result
def refresh_from_db(self, *args, **kwargs):
swap = False
if self.payer.id > self.payee.id:
swap = True
super().refresh_from_db(*args, **kwargs)
if swap:
self.do_swap()
Related
Can you please tell me how to transfer the calendar to the form with a get request, with clicking on the days of the week and transferring to the form? There is a filtering for all fields at the same time now, only with the date of trouble (
Approximately how such a calendar can be transmitted? Thanks in advance.
class Traveller(models.Model):
title = models.CharField(max_length=30,default='',null=False)
origin = models.ForeignKey(Origin,on_delete=models.CASCADE,max_length=100,verbose_name= 'Источник',default='')
destination = models.ForeignKey(Destination,on_delete=models.CASCADE, verbose_name="Местонахождение",default='')
transport = models.ForeignKey(Transport,on_delete=models.CASCADE, verbose_name="Транспорт",default='')
passengers = models.ForeignKey(Passengers,on_delete=models.CASCADE, verbose_name="Пассажиры",default='')
url = models.SlugField(max_length=130, unique=True)
def __str__(self):
return self.title
class Meta:
verbose_name = 'Путешествие'
verbose_name_plural = 'Путешествие'
def get_absolute_url(self):
return reverse("traveller", kwargs={"url": self.url})
`views:
class FullTraveller:
def get_origin(self):
return Origin.objects.all()
def get_destination(self):
return Destination.objects.all()
def get_transport(self):
return Transport.objects.all()
def get_passengers(self):
return Passengers.objects.all()
class TravellerView(FullTraveller, ListView):
template_name = 'index.html'
model = Traveller
queryset = Traveller.objects.all()
paginate_by = 1
class FilterTravelView(FullTraveller,ListView):
def get_queryset(self):
if self.request.GET.getlist("origin") and self.request.GET.getlist("destination") and self.request.GET.getlist(
"transport") and self.request.GET.getlist("destination"):
queryset = Traveller.objects.filter(origin__in=self.request.GET.getlist("origin"),
destination__in=self.request.GET.getlist("destination"),
transport__in=self.request.GET.getlist("transport"),
passengers__in=self.request.GET.getlist("passengers"))
else:
queryset = Traveller.objects.filter(Q(origin__in=self.request.GET.getlist("origin")) | Q(
destination__in=self.request.GET.getlist("destination")) | Q(
transport__in=self.request.GET.getlist("transport"))| Q(
passengers__in=self.request.GET.getlist("passengers")))
return queryset
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["origin"] = ''.join([f"origin={x}&" for x in self.request.GET.getlist("origin")])
context["destination"] = ''.join([f"destination={x}&" for x in self.request.GET.getlist("destination")])
context["transport"] = ''.join([f"transport={x}&" for x in self.request.GET.getlist("transport")])
context["passengers"] = ''.join([f"passengers={x}&" for x in self.request.GET.getlist("passengers")])
return context
forrm in template
`
```
```
I tried various options with widgets, but it didn’t work to insert them into the template
I am trying to count the visits to a view. I would like for the counter to increment by 1 every time someone calls up the view. Then, I want the "visits" field on the model to automatically update with the latest count. However, I am not sure how to implement this. Using some code I've found, I am trying this:
models.py
class Statute(models.Model):
address = models.ForeignKey(Address,
null = True)
statute_name = models.CharField(max_length=25,
default='')
category = models.CharField(max_length=55,
default='')
section_number = models.CharField(max_length=55,
default='')
section_title = models.CharField(max_length=255,
default='')
timestamp = models.DateTimeField(editable=False)
visits = models.IntegerField(default=0)
content = models.TextField(default='')
slug = models.SlugField()
views.py
def get_context_data(self, **kwargs):
context = super(LibraryInStateView, self).get_context_data(**kwargs)
state = State.objects.get(slug=self.kwargs.get('state'))
statute = Statute.objects.all()
context['latest_statutes'] = statute.filter(
address__zipcode__city__county__state=state).order_by(
'-timestamp')
context['statute_count'] = Statute.objects.filter(
address__zipcode__city__county__state=state).count()
context['view_count'] = self.request.session['visits']+1
return context
You can include it in .get_object() method in LibraryInStateView
def get_object(self):
statute = super().get_object()
statute.visits += 1
statute.save()
self.view_count = statute.visits
return statute
Or get method:
def get(self, request, *args, **kwargs):
statute = # ... code to retrieve Statute for this view
statute.visits += 1
statute.save()
self.view_count = statute.visits
return super().get(request, *args, **kwargs)
Then once you attached view_count to class instance, you can add it to context:
def get_context_data(self, **kwargs):
...
context['view_count'] = self.view_count
return context
In your view update:
statute = Statute.objects.filter(address__zipcode__city__county__state=state)
statute.visits += 1
statute.save()
context['statute_count'] = statute
Edit:
Since my question seems to be too long, I'll add the short version here:
I'm trying to design a class decorator that decorates a class method and Caches it's return value. But not based on the "self" argument, but "self.id".
More detailed version:
I have some models, lets say it's something like this:
class book(models.Model):
name = models.CharField(max_length=256)
...
class vote(models.Model):
book = models.ForeignKey( book )
is_up = models.BooleanField( default = False )
...
class comment(models.Model):
...
And I would like to have a decorator, ModelCacheAttr so that I can cache some expensive methods the first time they're used or calculated elsewhere.
Like the total number of upvotes, downvotes and the overall percentage.
Also, It's important to note that models should be cached based on their model_id rather than their python object id.
And, It should be possible to removed a cache, in-case it has been expired, likely in a related vote's post_save signal.
So lets extend the book class so that it covers all that I said:
class book(models.Model):
name = models.CharField(max_length=256)
...
def _update_votes(self):
U,D = 0,0
for v in vote.objects.filter( book_id = self.id ):
if v.is_up:
U+=1
else:
V+=1
self.percentage.set_cache( U / (U + D) if U + D > 0 else 50 )
self.up_votes.set_cache( U )
self.down_votes.set_cache( D )
#ModelCacheAttr
def percentage(self):
self._update_votes()
return self.percentage()
#ModelCacheAttr
def up_votes(self):
self._update_votes()
return self.up_votes()
#ModelCacheAttr
def down_votes(self):
self._update_votes()
return self.down_votes()
#ModelCacheAttr
def total_comments(self):
return comments.objects.filter( book_id = self.id ).count()
So far, this is what I've written, but i can't access "set_cache" and other methods of the decorator:
class ModelCacheAttr( object ):
def __init__(self, f):
self.f = f
self.Cache = {}
def __call__(self, model, *args, **kwargs):
print( self, type(self) )
if model.id in self.Cache:
return self.Cache[model.id]
R = self.f(model, *args, **kwargs)
self.Cache[model.id] = R
return R
def __get__(self, model, objtype):
"""Support instance methods."""
return partial(self.__call__, model)
def set_cache(self, model, value): #Doesn't work
if hasattr(model,'id'):
model = model.id
self.Cache[model] = value
def update_for(self, model): #Doesn't work
if hasattr(model,'id'):
model = model.id
self.Cache.pop(model, None)
I have an invoice app that has an ID. I'd like to have it rollover by year. That is, every year, it would start back at 001. My return string would be 2014-001, 2014-002, 2015-001...
However, Django doesn't seem to support composite keys. Is there a way to do this?
class Invoice(models.Model):
description = models.CharField(max_length=200)
date = models.DateField('Date Issued')
client = models.ForeignKey('organizations.Contact')
UNPAID = 'Unpaid'
PAID = 'Paid'
STATUS_CHOICES = (
(UNPAID, 'Unpaid'),
(PAID, 'Paid'),
)
status = models.CharField(max_length=6,
choices=STATUS_CHOICES, default=UNPAID)
notes = models.TextField(blank=True)
def __str__(self):
return (self.date.__str__().replace("-", "") +
"-" + self.id.__str__().zfill(3))
edit new code:
def save(self, *args, **kwargs):
if self.invoice_id is "":
current_date = self.date.year
previous_invoice = Invoice.objects.filter(invoice_id__contains=current_date).order_by('-id').first()
if previous_invoice is not None:
num = int(previous_invoice.invoice_id[5:])
else:
num = 1
self.invoice_id = '{year}-{num:03d}'.format(year=current_date, num=num)
super(Invoice, self).save(*args, **kwargs)
You can use a CharField for the id, setting it as primary key and generating it automatically upon saving.
Something similar to the following should work:
class Invoice(models.Model):
id = models.CharField(max_length=8, null=False, primary_key=True)
...
def save(self, *args, **kwargs):
if self.pk is None:
current_year = datetime.now().year
previous_invoice = Invoice.objects.filter(id__contains=str(year)).order_by('-id').first()
if previous_invoice is not None:
num = int(previous_invoice.id[5:])
else:
num = 1
self.pk = '{year}-{num:03d}'.format(year=current_year, num=num)
super(Invoice, self).save(*args, **kwargs)
EDIT: Add missing primary_key attribute in id field definition.
here's my model:
class MediumCategory(models.Model):
name = models.CharField(max_length=100, verbose_name=u"Nazwa")
slug = models.SlugField(blank=True)
parent = models.ForeignKey('self', blank=True, null=True, verbose_name=u"Rodzic")
parameters = models.ManyToManyField(AdvertisementDescriptonParameter, blank=True)
count_mediums = models.PositiveIntegerField(default=0)
count_ads = models.PositiveIntegerField(default=0)
descendants = models.ManyToManyField('self', blank=True, null=True)
def save(self, *args, **kwargs):
self.slug = slugify("%s_%s" % (self.id, self.name))
super(MediumCategory, self).save(*args, **kwargs)
def __unicode__(self):
return unicode(self.name)
here's my admin:
class MediumCategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'parent', 'count_mediums', 'count_ads']
def save_model(self, request, obj, form, change):
admin.ModelAdmin.save_model(self, request, obj, form, change)
update_category_descendants()
and here's the function:
def update_category_descendants(sender=None, **kwargs):
data = dict()
def children_for(category):
return MediumCategory.objects.filter(parent=category)
def do_update_descendants(category):
children = children_for(category)
descendants = list() + list(children)
l = list([do_update_descendants(child) for child in children])
for descendants_part in l:
descendants += descendants_part
if category:
data[category] = []
for descendant in descendants:
data[category].append(descendant)
return list(descendants)
# call it for update
do_update_descendants(None)
for k, v in data.iteritems():
k.descendants = v
print k, k.descendants.all()
what update_category_descendants does, is taking all descendands of node in the tree and saves it into descendants list of this node. Useful for browsing categorized products in store.
While print k, k.descendants.all() works as expected, in fact data is not saved in db.
when I do:
def category(request, category_slug, page=None):
cats = MediumCategory.objects.all()
category = MediumCategory.objects.get(slug=category_slug)
descendants = category.descendants.all()
print "category, descendants", category, descendants
descendants variable is always [].
What am I missing here?
In your final loop in the update_category_descendants function, I believe you need to make it:
for k, v in data.iteritems():
k.descendants.add(*v)
See also Django's related objects reference.