How to call the methods of the model in django template? - django

I have the model of Invoicetracker in Models.py and I want to call the methods of the model in the template.
Model.py
class Invoicetracker(models.Model):
TotalBillAmount = 0
invoicenumber = models.IntegerField()
invoicedate = models.DateTimeField()
BillAmount = models.IntegerField()
TotalPaid = models.IntegerField()
Remark = models.CharField(max_length=100)
def __str__(self):
return self.invoicenumber
def totalbill(self):
total = Invoicetracker.objects.all().aggregate( TotalBillAmount = Sum('BillAmount'))
return total
def totalpaid(self):
total = Invoicetracker.objects.all().aggregate(TotalPaid = Sum('TotalPaid'))
return total
views.py
def invoicemgt(request):
invoiceitem = Invoicetracker.objects.all()
return render(request, 'order.html',{'invoiceitem' : invoiceitem})
I want to call the method totalbill and totalpaid in the order.html template. Can I call this through instance method? or Shall we use an classmethod?

You can use #property decorator above your function like that:
#property
def totalbill(self):
total = Invoicetracker.objects.all().aggregate( TotalBillAmount = Sum('BillAmount'))
return total
#property
def totalpaid(self):
total = Invoicetracker.objects.all().aggregate(TotalPaid = Sum('TotalPaid'))
return total
After that, you can call those function inside your template like:
{{item.totalbill}}
{{item.totalpaid}}

You can simply call these in a variable. So if you passed an Invoicetracker object to a template with the name invoicetracker, you can render this with:
{{ invoicetracker.totalbill }}
Note that you can not use brackets here. If the item is a callable, the template will automatically call it without parameters. Methods that thus have parameters, can not be called, or at least not without some extra "tricks".
That being said, here your method does not depend on the self. So that makes it more fit for a #staticmethod or #classmethod. For example:
class Invoicetracker(models.Model):
# …
#classmethod
def totalbill(cls):
return cls.objects.aggregate(TotalBillAmount=Sum('BillAmount'))['TotalPaid']
#classmethod
def totalpaid(cls):
return cls.objects.aggregate(TotalPaid=Sum('TotalPaid'))['TotalPaid']
Then you can call these methods in your view, and pass the result to the template:
def some_view(request):
total_paid = Invoicetracker.totalbill()
total_bill = Invoicetracker.totalbill()
return render(request, 'order.html', {'total_paid': total_paid, 'total_bill': total_bill})

Related

Convert function base view to class base view in Django

how to convert these View to Class Base View, this view for add order new order and I wanna
change this View to Class Base but I cant
def add_user_order(request):
new_order_form = UserNewOrderForm(request.POST or None)
if new_order_form.is_valid():
order = Order.objects.filter(owner_id=request.user.id, is_paid=False).first()
if order is None:
order = Order.objects.create(owner_id=request.user.id, is_paid=False)
product_id = new_order_form.cleaned_data.get('product_id')
count = new_order_form.cleaned_data.get('count')
if count < 0:
count = 1
product = Product.objects.get_by_id(product_id=product_id)
order.orderdetail_set.create(product_id=product.id, price=product.price, count=count)
# todo: redirect user to user panel
return redirect(f'products/{product_id}/{product.title.replace(" ", "-")}')
return redirect('/')
You can make use of a FormView [Django-doc] where the form_valid function will do most of the logic:
class UserAddOrderView(FormView):
form_class = UserNewOrderForm
def form_valid(self, form):
order, __ = Order.objects.get_or_create(owner_id=request.user.id, is_paid=False)
self.product_id = product_id = new_order_form.cleaned_data.get('product_id')
count = new_order_form.cleaned_data.get('count')
if count < 0:
count = 1
order.orderdetail_set.create(product_id=product_id, price=product.price, count=count)
return super().form_valid(form)
def get_success_url(self):
return f'products/{product_id}/{product.title.replace(" ", "-")}'
It is however not a good idea to construct URLs manually. You can work with the reverse(…) function [Django-doc]. The same with slugs, do not construct slugs yourself, but use slugify(…) [Django-doc]. You should normally also save the slug to the model to which it belongs to make (fast) lookups possible.

Pass parameter to Django REST framework serializer to use with model

I have a class Object with a method state that takes a datetime parameter dt. How do I pass the datetime parameter from the URL to Object.state()?
The model:
class Object(models.Model):
def state(self, dt=datetime.datetime.now()) -> dict:
...stuff...
return {'dt': dt, 'other_stuff': stuff}
The view:
class ObjectDetail(generics.RetrieveAPIView):
queryset = models.Object.objects.all()
serializer_class = serializers.ObjectSerializer
def get_serializer_context(self):
return {'dt': self.request.query_params.get('dt', datetime.datetime.now())}
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
And the serializer classes:
class ObjectSerializer(serializers.HyperlinkedModelSerializer):
state = StateSerializer(read_only=True, context={'dt': self.context['dt']})
class Meta:
model = models.Object
fields = ('state')
class StateSerializer(serializers.Serializer):
dt = serializers.DateTimeField()
... other stuff...
As you can see I am trying to pass dt as extra context in the line state = StateSerializer(read_only=True, context={'dt': dt}) having set the context earlier in the view. The problem here is that when ObjectSerializer is initialized dt is not accessible via self.context['dt'] as self is not defined.
The solution is to make state a serializers.SerializerMethodField() and then define a get_state method. The new ObjectSeializer looks like this:
class ObjectSerializer(serializers.HyperlinkedModelSerializer):
state = serializers.SerializerMethodField()
class Meta:
model = models.Object
fields = ('state')
def get_state(self, obj):
state = obj.state(self.context['dt'])
state_serializer = StateSerializer(state)
return state_serializer.data

Decorator to cache expensive non-field django model attributes

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)

django custom manager with filter parameter

I would like to add a custom manager which can be called from a template, but does not affect the entire model (e.g. admin views) and which listens to a parameter set in the request (user_profile).
The following is what I have so far:
models.py:
class CurrentQuerySet(models.query.QuerySet):
def current(self):
return self.filter(id=1) ## this simplified filter test works..
class CurrentManager(models.Manager):
use_for_related_fields = True
def get_query_set(self):
return CurrentQuerySet(self.model)
def current(self, *args, **kwargs):
return self.get_query_set().current(*args, **kwargs)
For model B is defined:
objects = CurrentManager()
The template calls:
{% for b in a.b_set.current %}
But as soon as I try to pass a parameter to that filter (in this case a date stored on the user-profile) the method does not return any results.
e.g.:
models.py
class CurrentQuerySet(models.query.QuerySet):
def current(self,my_date):
return self.filter(valid_from__lte=my_date)
showA.html
{% for b in a.b_set.current(request.user.get_profile.my_date) %}
Instead of passing the parameter from the template, I have also tried to set this in the view.py
#login_required
def showA(request,a_id):
my_date = request.user.get_profile().my_date
a = A.objects.get(id=a_id)
t = loader.get_template('myapp/showA.html')
c = RequestContext(request,{'a':a,'my_date':my_date,})
return HttpResponse(t.render(c))
Which part am I missing (or misunderstanding) here?
Thanks
R
Edit
Here the models. As mentioned, in this example it's a simple 1:n relationship, but can also be m:n in other cases.
class A(models.Model):
#objects = CurrentManager()
a = models.CharField(max_length=200)
description = models.TextField(null=True,blank=True)
valid_from = models.DateField('valid from')
valid_to = models.DateField('valid to',null=True,blank=True)
def __unicode__(self):
return self.a
class B(models.Model):
#objects = models.Manager()
objects = CurrentManager()
a = models.ForeignKey(A)
b = models.CharField(max_length=200)
screenshot = models.ManyToManyField("Screenshot",through="ScreenshotToB")
description = models.TextField(null=True,blank=True)
valid_from = models.DateField('valid from')
valid_to = models.DateField('valid to',null=True,blank=True)
def __unicode__(self):
return self.b
Edit-2
The accepted answer works for at least one relationship.
In case of a more nested data model, this method seems not to deliver the expected results:
models.py
class C(models.Model):
objects = CurrentManager()
b = models.ForeignKey(A)
c = models.CharField(max_length=200)
description = models.TextField(null=True,blank=True)
valid_from = models.DateField('valid from')
valid_to = models.DateField('valid to',null=True,blank=True)
def __unicode__(self):
return self.c
views.py
#login_required
def showA(request,a_id):
a = A.objects.get(id=a_id)
my_date = request.user.get_profile().my_date
b_objects = a.b_set.current(my_date)
c_objects = b_objects.c_set.current(my_date)
t = loader.get_template('controltool2/showA.html')
c = RequestContext(request,{'a':a,'b_objects':b_objects,'c_objects':c_objects,})
return HttpResponse(t.render(c))
This returns the error: 'QuerySet' object has no attribute 'c_set'.
I'd simplify it:
class CurrentManager(models.Manager):
def current(self, my_date):
return super(CurrentManager, self).get_query_set().filter(valid_from__lte=my_date)
and then use it like this:
a = A.objects.get(id=a_id)
my_date = request.user.get_profile().my_date
b_objects = a.b_set.objects.current(my_date)
and then just pass a to the template as the filtered objects accessing them using this:
{% for b in b_objects %}
Hope this helps!
Edit (by requestor):
I had to adjust it as follows to get it working:
a = A.objects.get(id=a_id)
my_date = request.user.get_profile().my_date
b_objects = a.b_set.current(my_date)
This threw an error: "'RelatedManager' object has no attribute 'objects'"
a.b_set.objects.current(my_date)

Setting choices and forms at runtime for a Form Wizard?

I am trying to use Form Wizard but I can't figure out where to set the choices for the fields.
#views.py
class QuizWizard(SessionWizardView):
def done(self, form_list, **kwargs):
return render_to_response('done.html', {
'form_data':[form.cleaned_data for form in form_list],
})
#forms.py
class QuestionForm(forms.ModelForm):
#selection = forms.ChoiceField()
class Meta:
model = Question
I see an empty form that looks like the admin panel for adding an object.
I would like to be able to pass a question to the form and have the question field filled out and not editable and preferable not submitted.
If I do
(r'^(?P<quiz_id>\d+)', QuizWizard.as_view(get_form_list)),
the function get_form_list has no length
(r'^(?P<quiz_id>\d+)', QuizWizard.as_view(get_form_list(quiz_id))),
Quiz_id is unknown.
so now I am trying to pass quiz_id to the view function and generate the list of question forms to be used in the form wizard
urls.py
url(r'^(?P<quiz_id>\d+)', 'quiz.views.get_form_list'),
views.py
class QuizWizard(SessionWizardView):
def done(self, form_list, **kwargs):
return render_to_response('done.html', {
'form_data':[form.cleaned_data for form in form_list],
})
def get_form_list(request, quiz_id):
quiz = Quiz.objects.get(id=quiz_id)
question_forms = []
for question in quiz.questions.all():
choices = []
for choice in question.choices.all():
choices.append(choice)
f = QuestionForm(instance=question)
question_forms.append(f)
return QuizWizard.as_view(question_forms)(request)
I am getting the error message
issubclass() arg 1 must be a class
Update based on Rohan's answer:
def get_form_list(request, quiz_id):
quiz = Quiz.objects.get(id=quiz_id)
question_forms = []
for question in quiz.questions.all():
choices = []
for choice in question.choices.all():
choices.append(choice)
f = QuestionForm(instance=question)
question_forms.append(f)
inst_dict = {}
for idx, question in enumerate(question_forms):
inst_dict[str(idx)] = question
print inst_dict
#inst_dict = { str(index(x)) : x for x in question_forms}
QuestFormList = []
for i in range(len(question_forms)):
QuestFormList.append(QuestionForm)
QuizWizard.as_view(QuestFormList, instance_dict=inst_dict)(request)
With this code I am getting an error
'ModelFormOptions' object has no attribute 'many_to_many'
Here is my models.py
class Choice(models.Model):
choice = models.CharField(max_length=64)
def __unicode__(self):
return self.choice
#create a multiple choice quiz to start
class Question(models.Model):
question = models.CharField(max_length=64)
answer = models.CharField(max_length=64)
choices = models.ManyToManyField(Choice)
module = models.CharField(max_length=64)
def __unicode__(self):
return self.question
class Quiz(models.Model):
name = models.CharField(max_length=64)
questions = models.ManyToManyField(Question)
def __unicode__(self):
return self.name
You should call it using the class instead of object. So change your call to
QuizWizard.as_view(question_forms)(request)
Update:
The wizard view takes form class list as parameters not the form instance. You are creating form instances in question_forms and passing it to view.
If you want to pass instance for the form in each step, you can pass instance_dict.
Something like ...
inst_dict = { '0': question_forms[0], #step 0 instance
'1': question_forms[1], #step 1 instance
}
QuestFormList = [QuestionForm, QuestionForm ...]
QuizWizard.as_view([QuestFormList, instance_dict=inst_dict)(request)