Get the value in the intermediate model in a Through Relationship - python

As I loop through a Prize.objects.all() set, I am trying to get the quantity value from PrizeItem, but it seems to reference only the GameItem model, not giving access to the intermediate PrizeItem attributes.
How do I get access to the Through model's attributes?
MODELS:
class GameItem(models.Model):
'...'
class Prize(models.Model):
'...'
item = models.ManyToManyField(GameItem, through='PrizeItem')
class PrizeItem(models.Model):
#relations
game_item = models.ForeignKey(GameItem, on_delete=models.CASCADE)
prize = models.ForeignKey(Prize, on_delete=models.CASCADE)
#Item details
quantity = models.IntegerField(null=True, blank=True, default=1)
VIEWS:
def gameprizes(request):
prizes=Prize.objects.all()
context={'prizes':prizes}
return render(request, "the-app/game-prizes.html", context)
TEMPLATE:
{% if prize.item.all %}
<table class="table table-condensed">
<tbody>
{% for prize_item in prize.item.all %}
<tr>
<td>{{ prize_item.type }}</td><td>{{ prize_item.name }} {{ prize_item.quantity }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}

It gets a little ugly when it comes to intermediate models in querysets. One way you can address this would be:
class Prize(models.Model):
'...'
item = models.ManyToManyField(GameItem, through='PrizeItem')
def quantity(self):
return self.item.quantity
Note that this does lead to additional queries, and you can use prefetch_related in your query to optimize
Another option would be to use the template
For game item, you would be doing something like:
class GameItem(models.Model):
'...'
def quantity(self):
return self.prizeitem_set.first()

What the hell with the models?
~> Use the #karthikr answer
Just prepare in views.py (overwrite query_set)
Use (_set ~ join) in html
Have a nice day, don't make it hard, give the hardest part for the strongest (server - database)

Related

Is there a way to isolate a foreign key from its related model using django?

I am building a django website that displays the prices of items in a shop. Each item belongs to a category so I mande category a foreign key that can have one or more items.
`class Category(models.Model):
category = models.CharField(max_length=64)
def __str__(self):
return self.category
class ShopItem(models.Model):
itemName = models.CharField(max_length=64)
price = models.IntegerField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
def __str__(self):
return self.itemName`
However, in the html, I am unable to obtain both the category and the shop item
I tried the following...
`---snip---
{% used_category = [] %}
{% for item in shopitem_list %}
{% if item.category not in used_category %}
<tr>
<td>item.category</td>
<td></td>
<td></td>
</tr>
<tr>
{% for rel_item in shopitem_list %}
{% if rel_item.category == item.category %}
<td></td>
<td>rel_item.itemName</td>
<td>rel_item.category</td>
{% endif %}
{% enfor %}
</tr>
{% endif %}
{% endfor %}`
I was hoping this would help me create a table where all the items are sorted below their respective categories but I got an error instead:
TemplateSyntaxError at /
Invalid block tag on line 18: 'used_category', expected 'endblock'. Did you forget to register or load this tag?
You need to create a view function which will pass the required Models information to the Template, thus completing the Model - View - Template (MVT) method which django runs on.
You can't create and update a list within the template, but you can prepare the data in the view function, then iterate over your items differently.
Start by making sure the categories are in your template context. To avoid n+1 queries, prefetch the related items also. Let's say your view looks like this (updated):
from django.shortcuts import render
from .models import Category
def items_list(request):
context = {
"categories": Category.objects.exclude(shop_items=None).prefetch_related(
"shop_items"
)
}
return render(request, "my_template.html", context)
Then in the template iterate over the categories:
{% for category in categories %}
<tr>
<td colspan="3">{{ category.category }}</td>
</tr>
<tr>
{% for item in category.shop_items %}
<td></td>
<td>{{ item.itemName }}</td>
<td>{{ item.price }}</td>
{% enfor %}
</tr>
{% endfor %}
NB The related name shop_items may not work here. To make sure it does, you can manually set the related_name on your foreign key. At the same time I would suggest changing itemName to just name:
class ShopItem(models.Model):
name = models.CharField(max_length=64)
price = models.IntegerField()
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="shop_items")
def __str__(self):
return self.name

Generate HTML Table that has Rows and Columns as Models in django with normal time complexity

I would like to populate a table in a template, but I noticed that its extremely slow and i think that I am doing something terribly wrong (to be specific ... it take 3 seconds to handle one request from my localhost). I think that the whole reason of it being so slow is because of I am generating it over and over again ... which in my opinion is very bad thing to do ... sadly, I would like it to be pulled from DB in case of any problems (like server restart etc.) so thats why i do not want to initialise it as a global var and work with it that way.
models.py
class Category(models.Model):
name = models.CharField(max_length=120)
class Note(models.Model):
name = models.CharField(max_length=120)
class Whistle(models.Model):
name = models.CharField(max_length=120)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
note = models.ForeignKey(Note, on_delete=models.CASCADE)
views.py
class ProductTableView(TemplateView):
template_name = "whistles_shop/table.html"
def get_context_data(self, *args, **kwargs):
request = self.request
context = super().get_context_data(**kwargs)
context['whistle'] = Whistle.objects.all()
context['note'] = Note.objects.all()
context['category'] = Category.objects.all()
all_rows = []
for cat in Category.objects.all():
one_row = [cat.name]
for note in Note.objects.all():
whist = Whistle.objects.filter(category=cat, note=note)
if whist:
one_row.append("X")
else:
one_row.append("-")
all_rows.append(one_row)
context['tab'] = all_rows
return context
template.html
<table class="table table-dark">
<thead>
<tr>
<th>#</th>
{% for obj in note %}
<th>{{obj.name}}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in tab %}
<tr>
{% for x in row %}
<th> {{x}} </th>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
Any suggestions on how to fix the speed would be awesome <3. Thanks in advance :)
Thx for the downvotes that were acctually unspecified... anyways ... for ppl that are wondering, I managed to male it go faster with putting the query calls outside of the forloop (by swaping database filter for lambda filters. If anyone has still even better approach, I will gladly welcome them ^^ Also, I am thinking about putting all the data to frontend and let the JS handle it, but i feel like thats just an overkilling :/ so we will see

Django accessing Model with property decorator

How do I access the data in the method with the property decorator. I can access it in the shell, and it is what I need, but it comes up blank in my site.
Models.py:
class Project(models.Model):
date_published = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
area = models.ForeignKey(Area, on_delete=models.PROTECT)
title = models.CharField(max_length=128, unique=True)
summary = models.CharField(max_length=256)
others = models.CharField(max_length=128, blank=True)
deadline = models.DateField(null=True, blank=True)
priority = models.ForeignKey(Priority, on_delete=models.PROTECT)
closed = models.DateTimeField(null=True, blank=True)
#property
def updates(self):
updates = []
categories = set(self.update_set.all().values_list(
'category__id', flat=True))
for cat_id in categories:
updates.append(Update.objects.filter(
project=self, category__id=cat_id).order_by('added').last())
return updates
def __str__(self):
return self.title
Views.py:
class ProjectView(ListView):
template_name = 'project_portal/home.html'
queryset = Project.objects.all()
And I am trying to use the following in my template:
<div class="box5">
{% for item in object_list %}
<table>
<tr>
<td>{{ item.updates }}</td>
</tr>
</table>
{% endfor %}
</div>
So far the box is blank. However, I have managed to get this data in the Django shell with the following:
p = Project.objects.all()[0]
p.updates
This returns the correct data in the right order for the first project. What do I need to do to get it to appear in the site?
It looks like your problem is that you are using .values() in your queryset in your view. This will give you a list. The reason why it works in the shell is because you are working with model objects, and hence you are able to get the updates property. So remove .values() from queryset = Project.objects.all().values()
Read up more about .values() here.
Are you sure the template has a var named project? Try changing project for object_list or override the get_context_data method to pass project to the template.
Edit: After reading your edited question I think you want to do something like this.
<div class="box5">
{% for project in object_list %}
<table>
<tr>
{% for category_update in project.updates %}
<td>{{ category_update }}</td>
{% endfor %}
</tr>
</table>
{% endfor %}
</div>
If you keep watching the category update in a wrong way please share with us your Category Model.
So this is now solved. The Models.py and Views.py in the original post are correct now, what was causing the rest of the issue was the Template, here is a short version of what was needed:
{% block home %}
{% if object_list %}
{% for project in project_list %}
<div class="box1">
<h4>{{ project.title }}</h4>
<p>Project Status</p>
<div class="square"></div>
</div>
<div class="box5">
{% for item in project.updates %}
<table>
<tr>
<td>{{ item }}</td>
</tr>
</table>
{% endfor %}
</div>
{% endfor %}
{% endif %}
The point I am making here is that there are two loops, one loops through projects in the object_list, and the second loop loops through items within the project.updates, which is fed by the method with the property decorator. I hope this is clear if anyone finds this looking for help.

Saving class-based view formset items with a new "virtual" column

I have a table inside a form, generated by a formset.
In this case, my problem is to save all the items after one of them is modified, adding a new "virtual" column as the sum of other two (that is only generated when displaying the table, not saved).
I tried different ways, but no one is working.
Issues:
This save is not working at all. It worked when it was only one form, but not for the formset
I tried to generate the column amount as a Sum of box_one and box_two without success. I tried generating the form this way too, but this is not working:
formset = modelformset_factory(
Item, form=ItemForm)(queryset=Item.objects.order_by(
'code__name').annotate(amount=Sum('box_one') + Sum('box_two')))
This issue is related to this previous one, but this new one is simpler:
Pre-populate HTML form table from database using Django
Previous related issues at StackOverflow are very old and not working for me.
I'm using Django 2.0.2
Any help would be appreciated. Thanks in advance.
Current code:
models.py
class Code(models.Model):
name = models.CharField(max_length=6)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
class Item(models.Model):
code = models.ForeignKey(Code, on_delete=models.DO_NOTHING)
box_one = models.IntegerField(default=0)
box_two = models.IntegerField(default=0)
class Meta:
ordering = ["code"]
views.py
class ItemForm(ModelForm):
description = CharField()
class Meta:
model = Item
fields = ['code', 'box_one', 'box_two']
def save(self, commit=True):
item = super(ItemForm, self).save(commit=commit)
item.box_one = self.cleaned_data['box_one']
item.box_two = self.cleaned_data['box_two']
item.code.save()
def get_initial_for_field(self, field, field_name):
if field_name == 'description' and hasattr(self.instance, 'code'):
return self.instance.code.description
else:
return super(ItemForm, self).get_initial_for_field(
field, field_name)
class ItemListView(ListView):
model = Item
def get_context_data(self, **kwargs):
data = super(ItemListView, self).get_context_data()
formset = modelformset_factory(Item, form=ItemForm)()
data['formset'] = formset
return data
urls.py
app_name = 'inventory'
urlpatterns = [
path('', views.ItemListView.as_view(), name='index'),
item_list.html
...
<div>
<form action="" method="post"></form>
<table>
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<thead>
<tr>
{% if forloop.first %}
<th>{{ form.code.label_tag }} </th>
<th>{{ form.description.label_tag }} </th>
<th> <label>Amount:</label> </th>
<th>{{ form.box_one.label_tag }} </th>
<th>{{ form.box_two.label_tag }} </th>
{% endif %}
</tr>
</thead>
<tbody>
<tr>
<td>{{ form.code }}</td>
<td>{{ form.description }}</td>
<td>{{ form.amount }}</td>
<td>{{ form.box_one }}</td>
<td>{{ form.box_two }}</td>
</tr>
</tbody>
{% endfor %}
<input type="submit" value="Update" />
</table>
</form>
</div>
...
Annotating query with virtual column
Sum is an aggregate expression and is not how you want to be annotating this query in this case. Instead, you should use an F exrepssion to add the value of two numeric fields
qs.annotate(virtual_col=F('field_one') + F('field_two'))
So your corrected queryset would be
Item.objects.order_by('code__name').annotate(amount=F('box_one') + F('box_two'))
The answer provided by cezar works great if intend to use the property only for 'row-level' operations. However, if you intend to make a query based on amount, you need to annotate the query.
Saving the formset
You have not provided a post method in your view class. You'll need to provide one yourself since you're not inheriting from a generic view that provides one for you. See the docs on Handling forms with class-based views. You should also consider inheriting from a generic view that handles forms. For example ListView does not implement a post method, but FormView does.
Note that your template is also not rendering form errors. Since you're rendering the formset manually, you should consider adding the field errors (e.g. {{ form.field.errors}}) so problems with validation will be presented in the HTML. See the docs on rendering fields manually.
Additionally, you can log/print the errors in your post method. For example:
def post(self, request, *args, **kwargs):
formset = MyFormSet(request.POST)
if formset.is_valid():
formset.save()
return SomeResponse
else:
print(formset.errors)
return super().post(request, *args, **kwargs)
Then if the form does not validate you should see the errors in your console/logs.
You're already on the right path. So you say you need a virtual column. You could define a virtual property in your model class, which won't be stored in the database table, nevertheless it will be accessible as any other property of the model class.
This is the code you should add to your model class Item:
class Item(models.Model):
# existing code
#property
def amount(self):
return self.box_one + self.box_one
Now you could do something like:
item = Item.objects.get(pk=1)
print(item.box_one) # return for example 1
print(item.box_two) # return for example 2
print(item.amount) # it will return 3 (1 + 2 = 3)
EDIT:
Through the ModelForm we have access to the model instance and thus to all of its properties. When rendering a model form in a template we can access the properties like this:
{{ form.instance.amount }}
The idea behind the virtual property amount is to place the business logic in the model and follow the approach fat models - thin controllers. The amount as sum of box_one and box_two can be thus reused in different places without code duplication.

Django View check second model for value

I'm somewhat new to Django so please bear with me here. I have a three models in my app, Company, Articles, and Transcripts. Both Articles and Transcripts have a foreignkey() field that links to the Company model. In my main view I list all of the companies, but I would like to indicate if there is an article or transcript for each of those companies.
Here are the models:
class Company(models.Model):
stock_symbol = models.CharField(max_length=5, unique=False)
company_name = models.CharField(max_length=200)
address = models.CharField(max_length=300, blank=True)
website = models.URLField(max_length=200, blank=True)
following = models.BooleanField()
class Articles(models.Model):
company = models.ForeignKey('Company', related_name='articles')
headline = models.CharField(max_length=200)
url = models.URLField(max_length=300, blank=True)
class Transcripts(models.Model):
company = models.ForeignKey('Company', related_name='transcripts')
headline = models.CharField(max_length=200)
url = models.URLField(max_length=300, blank=True)
Below is a view for a single company, however since I'm returning all companies I obviously can't use a primarykey. How would I go about getting this info for each of the companies? Am I setting up my models the right way to do this?
class CompanyDetails(generic.DetailView):
model = Company
template_name = 'company_details.html'
def get_context_data(self, **kwargs):
pk = self.kwargs.get('pk')
context = super(CompanyDetails, self).get_context_data(**kwargs)
context['company'] = Company.objects.filter(id=pk)
context['articles'] = Articles.objects.filter(company_id=pk).order_by('-date')
context['transcripts'] = Transcripts.objects.filter(company_id=pk).order_by('-date')
return context
UPDATE
How do I update the template if I want to show in a column that there is at least one article tied to each company? When I tried the for loop in the answer it created a column for every article, so I tried changing it to the below but it every records shows up as "Blank".
<tbody>
{% for company in all_companies %}
<tr>
<td nowrap="true">{{ company.company_name }}</td>
<td nowrap="true">{{ company.stock_symbol }}</td>
{% if company.following %}
<td align="center"><span class="glyphicon glyphicon-star"></span></td>
{% else %}
<td></td>
{% endif %}
<!--HAS ARTICLE(S) FLAG -->
{% if article in company.articles.all %}
<td align="center"><span class="glyphicon glyphicon-list-alt"></span></td>
{% else %}
<td>Blank</td>
{% endif %}
<td nowrap="true">{{ company.website }}</td>
<td nowrap="true">{{ company.address }}</td>
</tr>
{% endfor %}
</tbody>
In the template, you can follow the foreign key backwards from a company to its articles or transcripts.
{{ company }}
{% for company in company.articles.all %}
{{ article }}
{% endfor %}
If you use this in your detail view, you don't have to set articles or transcripts in the get_context_data method. You shouldn't set context['company'] at all - the detail view does this for you.
In the list view, you can loop through the companies and access the articles and transcripts in the same way.
{% for company in company_list %}
{{ company }}
{% for company in company.articles.all %}
{{ article }}
{% endfor %}
{% endfor %}
This will cause a query for every single company in the queryset. You can prevent this by using prefetch_related.
Company.objects.prefetch_related('articles', 'transcripts')
In your list view, you can use prefetch_related by setting queryset:
class CompanyList(generic.ListView):
# No need to set model now that you have set queryset
queryset = Company.objects.prefetch_related('articles', 'transcripts')
If you just want to know whether or not there are associated articles or transactions, then you don't have to prefetch them. You can annotate the queryset instead:
from django.db.models import Count
# in your view
queryset = Company.objects.annotate(num_articles=Count('article')
Then in your template, you can access company.num_articles.
{% if company.num_articles %}
Company has articles
{% endif %}

Categories