Cannot render model fields in forloop with annotate() and values() (Django) - python

I am using .values() and .annotate()to sum up 2 models fields based on matching criteria.
I wrapped this in a forloop in my template to iterate.
Problem: I cannot call the model fields anymore. The forloop returns the venue_id instead of the name and the usual approach to call the logo does not work anymore.
(these were rendering fine before I used .values() and .annotate(). Makes me think I am missing something in the logic here. Any ideas?
Models
class Venue(models.Model, HitCountMixin):
id = models.AutoField(primary_key=True)
name = models.CharField(verbose_name="Name",max_length=100, blank=True)
logo = models.URLField('Logo', null=True, blank=True)
class Itemised_Loyatly_Card(models.Model):
user = models.ForeignKey(UserProfile, blank=True, null=True, on_delete=models.CASCADE)
venue = models.ForeignKey(Venue, blank=True, null=True, on_delete=models.CASCADE)
add_points = models.IntegerField(name = 'add_points', null = True, blank=True, default=0)
use_points = models.IntegerField(name= 'use_points', null = True, blank=True, default=0)
Views
from django.db.models import Sum, F
def user_loyalty_card(request):
itemised_loyalty_cards = Itemised_Loyatly_Card.objects.filter(user=request.user.id).values('venue').annotate(add_points=Sum('add_points')).annotate(use_points=Sum('use_points')).annotate(total=F('add_points')-F('use_points'))
return render(request,"main/account/user_loyalty_card.html", {'itemised_loyalty_cards':itemised_loyalty_cards})
Templates
{%for itemised_loyatly_card in itemised_loyalty_cards %}
<img"src="{{itemised_loyatly_card.venue.logo}}">
{{itemised_loyatly_card.venue}}
{{itemised_loyatly_card.total}}
{%endfor%}
Renders

It returns only the value ID because that is the only field you passed in as an argument to .values('venue'). Whereas if you want another attribute you have to use field lookups, like so:
.values('venue__name'. 'venue__logo')
Also, .values() returns a QuerySet that returns dictionaries and not object instances, so if you pass that data into the template you can not access related data, meaning
{{itemised_loyatly_card.venue.logo}} will not work, but taking the first codeblock as an example {{itemised_loyatly_card.venue__logo}} works.
Another "mistake" is at these two annotations, annotations are a per object aggregation, these two lines are redundant, your model already has these fields.
.annotate(add_points=Sum('add_points'))
.annotate(use_points=Sum('use_points'))
That being said, if you want objects instead of values, that leaves you with:
views.py
itemised_loyalty_cards = (
Itemised_Loyatly_Card.objects.filter(user=request.user.id)
.annotate(total=F('add_points')-F('use_points'))
)
template.html
{%for itemised_loyatly_card in itemised_loyalty_cards %}
<p>
{{itemised_loyatly_card.venue.name}}
{{itemised_loyatly_card.add_points}}-
{{itemised_loyatly_card.use_points}}=
{{itemised_loyatly_card.total}}
</p>
<img src="{{itemised_loyatly_card.venue.logo}}" alt="" >
{%endfor%}

Related

Django - How to render a ModelForm with a Select field, specifying a disabled option?

I have the following models:
# Get or create a 'Not selected' category
def get_placeholder_categoy():
category, _ = ListingCategories.objects.get_or_create(category='Not selected')
return category
# Get default's category ID
def get_placeholder_category_id():
return get_placeholder_categoy().id
class ListingCategories(models.Model):
category = models.CharField(max_length=128, unique=True)
def __str__(self):
return f'{self.category}'
class Listing(models.Model):
title = models.CharField(max_length=256)
seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name='listings')
description = models.TextField(max_length=5120, blank=True)
img_url = models.URLField(default='https://media.istockphoto.com/vectors/no-image-available-picture-coming-soon-missing-photo-image-vector-id1379257950?b=1&k=20&m=1379257950&s=170667a&w=0&h=RyBlzT5Jt2U87CNkopCku3Use3c_3bsKS3yj6InGx1I=')
category = models.ForeignKey(ListingCategories, on_delete=models.CASCADE, default=get_placeholder_category_id, related_name='listings')
creation_date = models.DateTimeField()
base_price = models.DecimalField(max_digits=10, decimal_places=2, validators=[
MinValueValidator(0.01),
MaxValueValidator(99999999.99)
])
With these, I have the following form:
class ListingForm(ModelForm):
class Meta:
model = Listing
exclude = ['seller', 'creation_date']
widgets = {
'title': TextInput(attrs=base_html_classes),
'description': Textarea(attrs=base_html_classes),
'img_url': URLInput(attrs=base_html_classes),
'category': Select(attrs=base_html_classes),
'base_price': NumberInput(attrs=base_html_classes)
}
One of the available categories I have is "Not selected", since I want to allow that if at some point a category were to be removed, items can be reassigned to that one, however, when rendering the form, I will do some validation on the view function to prevent it from being submitted if the "not selected" category is sent with the form.
Because of this, I want the HTML form on the template to assign the 'disabled' attribute to the option corresponding to that category, however, I have been searching for a couple of days now without finding anything that I was able to understand to the point where I could try it.
Ideally, another thing I'd like to achieve is to be able to modify the order of the rendered options on the form so that I can move to the top 'not selected' regardless of its primary key within the model.
I am aware I can just create a form instead of a model form, or just modify the template so I manually specify how to render the form itself, but I do feel like there is a simple fix to this either on the model or on the model form that I am just not finding yet.
Thanks in advance!
I would suggest you use (in model definition)
class Listing(models.Model):
..
category = model.ForeignKey(ListingCategories, on_delete=models.SET_NULL, null=True, related_name='listings')
..
and optionally in form definition
class ListingForm(ModelForm):
category = forms.ModelChoiceField(ListingCategories, empty_label='Not Selected')
..
While rendering model form, a required attribute will be automatically added, and in form validating, it is also required. It is only in database validation that the field can be left NULL

Django Passing Through List Items

I have a todo website that allows users to put a remind in a certain list, such as work, school, groceries, etc. However, I'm a bit lost on how to get the list name and their items to display.
Models.py:
class RemindList(models.Model):
parent_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=50)
class Reminder(models.Model):
remind_types = [('Regular', 'Regular'), ('Long Term', 'Long Term')]
title = models.CharField(max_length=100)
description = models.TextField()
remind_time = models.DateTimeField(blank=True)
parent_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
parent_list = models.ForeignKey(RemindList, on_delete=models.CASCADE, null=True)
type_of_remind = models.CharField(max_length=12, choices=remind_types, default='Regular')
complete = models.BooleanField(default=False)
Views.py:
#login_required(login_url='/login')
def home(request):
user = get_object_or_404(User, username=request.user.username)
context = {
'events': ToDoItem.objects.filter(parent_user=user),
'reminders': Reminder.objects.filter(parent_user=user, type_of_remind='Regular'),
'long_term_reminders': Reminder.objects.filter(parent_user=user, type_of_remind='Long Term'),
'remind_list_items': RemindList.objects.filter(parent_user=user),
}
return render(request, 'main/home.html', context)
I can pass through the list names, and I planned to just loop through them and add Reminder.objects.filter(parent_user=user, type_of_remind='Regular', parent_list=list_name) to context. However, theres no way to loop through them on the html side (can't do for loop because there are other context types), and you can't filter them on the html side (correct me if I'm wrong). Is there another way to do this?
Ok, it took me a few readings, but if what I understand is correct you want to be able to iterate over the ReminderList objects and also list out the Reminder items under each one.
My suggestion would be to add a method to ReminderList that returns the items in that list, you could then do something like this in your template
{% for list in reminder_lists %}
... List header stuff goes here ...
{% for item in list.get_reminder_items %}
... Print the item ...
{% endfor %}
{% endfor %}
(The Django templating language can be a little interesting in that object.identifier can map to either an attribute or an object method - this can be useful for cases like these).

Sum of fields for filtered queryset using django_filters

I have the following view
class AuthorList(FilterView):
model = Author
filterset_class = AuthorFilter
context_object_name = 'authors'
In the template, one of the field is {{ author.value }}, which is an integer.
What I would like to do is to show the sum of all {{ author.value }} in my template, but in a dynamic way (if some filters are used, the sum is updated with the current Queryset).
I have tried adding extra context with get_context_data but I couldn't find out how to make it in a dynamic way.
EDIT
tried this, still not working:
#property
def get_sum_values(self):
sum_values = self.objects.all().aggregate(Sum('value'))['value__sum']
return sum_values
and in the template: {{ authors.get_sum_values }}
I have also tried to add a print statement in the property, but nothing shows up, so I guess the function is not even loaded.
my models:
class Author(models.Model):
name = models.CharField(max_length=50, blank=True, null=True)
value = models.IntegerField(null=True, blank=True)
Have you tried doing the sum in the model as a function ?
#property
def wallet_amount_guests(self):
data_payments = self.user.earnings_set.filter(transaction_type='g').aggregate(Sum('amount'))['amount__sum']
if data_payments == None:
data_payments = 0
return data_payments
The above is just an example I have used before. You can then call in the html as blah.function_name

django get distinct value from queryset

I have a model:
class Organisation(User):
organisation_name = models.CharField(max_length=200)
first_parent = models.ForeignKey('Parent', related_name="first", blank=True, null=True)
second_parent = models.ForeignKey('Parent', related_name="second", blank=True, null=True)
def __unicode__(self):
return self.username
and my Parent model:
class Parent(models.Model):
parent_name = models.CharField(max_length=50)
def __unicode__(self):
return self.parent_name
Here I want the list of parents in my templates that are unique. I dont want repeted parents.
I have gone through:
organisations = Organisation.objects.all().distinct('first_parent')
but this is not supported by mysql.
I tried to get list of first parents from organisations then its set gives only unique value like:
organisations = Organisation.objects.all()
parent_list = organisations.first_parent ## it gives error says 'QuerySet' object has no attribute 'first_parent'
I have thought about grouping in template but i was not able to..
What I want is list of first_parent or organisation with its id in template so that I can redirect it to some page with id like
{% for parent in parent_list %}
{{parent}}
<a href="{% url "some_url" parent.id %}"
first_parent I get should be unique.
How can I get this. please help
For backend that do not support distinct('...') like MySQL, use Organisation.objects.values('first_parent').distinct(),
this gives you a list of {'first_parent': value} dictionaries
To get a list of just the values do
parent_list = [org['first_parent'] for org in Organisation.objects.values('first_parent').distinct()]

compare two fields data from database

models.py
class ReportType(models.Model):
report = models.ForeignKey(Report)
title = models.CharField('Incident Type', max_length=200)
class Report(models.Model):
user = models.ForeignKey(User, null=False)
app_uuid = models.CharField('Unique App Id', max_length=100)
class Types(models.Model):
user = models.ForeignKey(User, null=True)
title = models.CharField('Incident Type', max_length=200)
is_active = models.BooleanField('Is Active', default=True)
In Types table,i am saving some default data in title field.The data entered by user are saved in ReportType table.
I want to compare the data in title field in Types model and ReportType model.After comparison,if title field data in ReportType model is not present in Types model,i need to display that in template.I need to show the non matching value present in ReportType model.
template.html
{% for type in report_type %}
{{type.title}}{% endfor %}
I tried with this query
report_type = Report.objects.filter(reporttype__title=F('types__title'))
I am getting this error "Cannot resolve keyword 'types' into field",this is because Types table don't have relation with Report table.Need help.
It seems you need at least 2 queries then:
# get all types as a list
all_types = Types.objects.values_list('title', flat=True)
# you need to show the mismatch, then use exclude()
report_type = ReportType.objects.exclude(title__in=all_types)
Hope it helps.
You can filter ReportType objects and get Reports from result queryset like this:
ReportType.objects.values_list('report').filter(title__in=Types.objects.values_list('title', flat=True).distinct())

Categories