Problem in displaying related objects in template for detail view - django

I want to render every {{ episode.object }} in single video.html page where it works fine for {{ video.object }}. But it isn't showing anything for episode object.. The final template video.html that I want to render episode objects can be seen here https://ibb.co/K9NMXtS
I tried
{% for episode in episodes %}
{{ episode.title }}
{% endfor %}
But that didn't worked. Here is the other configurations:-
#models.py
class Video(models.Model):
title = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=200, unique=True)
year = models.CharField(max_length=4)
category = models.CharField(max_length=3)
trailer = models.URLField(default='')
def __str__(self):
return self.title
def get_absolute_url(self):
from django.urls import reverse
return reverse("video.html", kwargs={"slug": str(self.slug)})
class Episode(models.Model):
video = models.ForeignKey(Video, related_name='episodes', on_delete=models.CASCADE)
title = models.CharField(max_length=512)
air_date = models.DateField()
videolink = models.URLField(default='')
def __str__(self):
return self.title
# urls.py
urlpatterns = [
path('video/<slug>/', views.VideoDetail.as_view(), name='videos'),
]
# view.py
class VideoDetail(DetailView):
model = Video
template_name = 'video/video.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args,**kwargs)
context['Episodes'] = Episode.objects.all()
return context

Python and Django templates are case sensitive. You use Episodes in the view, which doesn't match episodes in the template. Change one of them so that it matches (episodes is recommended for model instances in Python/Django).
Next, you are using Episode.objects.all() at the moment, which will display all episodes. If you only want the episodes for that video, then filter the queryset:
context['episodes'] = Episode.objects.filter(video=self.object)
Or you can get the same result by following the foreign key backwards:
context['episodes'] = self.object.episodes.all()

add VideoDetail, self in super tag
and while calling in template {{ episode.video.slug }}
and slug enough in url
path('video/<slug>/', views.VideoDetail.as_view(), name='videos'),
class VideoDetail(DetailView):
model = Episode
template_name = 'video/video.html'
def get_context_data(self, *args, **kwargs):
context = super(VideoDetail, self).get_context_data(*args,**kwargs)
context['Episodes'] = Episode.objects.all()
return context

Related

Dynamically render select choices Django

I posted a screenshot below to hopefully make this easier to understand so I will reference it in my question.
I am trying to create a recipe cost calculator app. I have 3 model namely Recipe, RecipeIngredient and Ingredient
The user will add ingredients to the database and then create recipes using those ingredients throught the RecipeIngredient model.
When creating an Ingredient the user selects a unit eg. (grams). Now when the user goes to create a Recipe (see screenshot below) I want to only display the units that are relevant to that ingredient and not all of them for eg. A user added Beef and the unit was grams now when the user is adding a RecipeIngredient to the Recipe I want them to only be able to select from the "Weight" category in the example (see screenshot below). The reason for this is because grams can't be converted to milliliters and so it shouldn't even be a choice.
If anything is unclear or you need more information just let me know.
Models
Recipe Models
from django.db import models
from django.urls import reverse
from django.contrib.auth import get_user_model
from ingredients.models import Ingredient
class Recipe(models.Model):
"""Store recipe information and costs"""
class Meta:
"""Meta definition for Recipe."""
verbose_name = "Recipe"
verbose_name_plural = "Recipes"
ordering = ("name",)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
name = models.CharField(max_length=155)
yield_count = models.PositiveIntegerField()
yield_units = models.CharField(max_length=155, default="servings")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("recipe:detail", kwargs={"id": self.pk})
def get_update_url(self):
return reverse("recipe:update", kwargs={"id": self.pk})
class RecipeIngredient(models.Model):
"""Holds more information about how much of a certain ingredient is used inside a recipe"""
class Meta:
"""Meta definition for RecipeIngredient"""
verbose_name = "RecipeIngredient"
verbose_name_plural = "RecipeIngredients"
# NOTE: User should first choose the ingredient
recipe = models.ForeignKey(Recipe, related_name="ingredients", on_delete=models.CASCADE)
ingredient = models.ForeignKey(Ingredient, related_name="ingredient", on_delete=models.CASCADE)
# NOTE: Then the amount an unit should be asked based on that
amount = models.DecimalField(max_digits=20, decimal_places=2)
unit = models.CharField(max_length=10, choices=Ingredient.UNIT_CHOICES, default=None)
def __str__(self):
"""Unicode representation of RecipeIngredient"""
return self.ingredient.__str__()
Ingredient Model
from django.db import models
from django.urls import reverse
from django.contrib.auth import get_user_model
from pint import UnitRegistry
from .choices import ALL_UNIT_CHOICES
class Ingredient(models.Model):
"""Stores data about an ingredient to be used by a Recipe model."""
class Meta:
"""Meta definition for Ingredient."""
verbose_name = "Ingredient"
verbose_name_plural = "Ingredients"
ordering = ("name",)
# Unit choices that will display in a dropdown
# It will be ordered by group (eg. Weight, Volume)
UNIT_CHOICES = ALL_UNIT_CHOICES
ureg = UnitRegistry()
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
name = models.CharField(max_length=50, unique=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
size = models.DecimalField(max_digits=10, decimal_places=2)
unit = models.CharField(max_length=10, choices=UNIT_CHOICES, default=None)
def __str__(self):
"""Unicode representation of Ingredient."""
return self.name
def get_absolute_url(self):
"""Return absolute url for Ingredient"""
return reverse("ingredient:detail", kwargs={"id": self.pk})
def get_update_url(self):
"""Return the url for update page"""
return reverse("ingredient:update", kwargs={"id": self.pk})
def get_delete_url(self):
return reverse("ingredient:delete", kwargs={"id": self.pk})
def save(self, *args, **kwargs):
self.unit = self.ureg.Unit(self.unit)
super(Ingredient, self).save(*args, **kwargs)
Views
def create_recipe(request):
template_name = "recipes/create.html"
form = CreateRecipeForm(request.POST or None)
ingredient_choice_form = IngredientChoiceForm(request.POST or None, request=request)
Formset = modelformset_factory(RecipeIngredient, form=CreateRecipeIngredientForm, extra=0)
formset = Formset(request.POST or None, queryset=Recipe.objects.none())
context = {
"form": form,
"ingredient_choice_form": ingredient_choice_form,
"formset": formset,
}
print(request.POST)
return render(request, template_name, context)
# This is the view being calle by HTMX everytime an ingredient is selected
def get_recipe_ingredient_details(request):
template_name = "recipes/hx_snippets/add_recipe_ingredient_details.html"
ingredient_id = request.GET.get("ingredients")
if not ingredient_id:
return HttpResponse("")
ingredient = Ingredient.objects.get(id=ingredient_id)
choices = get_unit_group(ingredient.unit)
Formset = modelformset_factory(RecipeIngredient, form=CreateRecipeIngredientForm, extra=1)
formset = Formset(
request.POST or None,
queryset=RecipeIngredient.objects.none(),
initial=[
{
"ingredient": ingredient,
"unit": choices,
},
],
)
context = {
"formset": formset,
"ingredient": ingredient,
}
Templates
Template returned by HTMX request
<p>
{% for form in formset %}
{{ form }}
{% endfor %}
</p>
main create template
{% block content %}
<h1>Create Recipe</h1>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<h3>Add ingredients</h3>
{{ formset.management_form }}
<div id="ingredient-form"></div>
{{ ingredient_choice_form.as_p }} <-- this is an HTMX get request that calls the get_recipe_ingredient_details view
<p><button type="submit">Create</button></p>
</form>
{% endblock content %}
{% block scripts %}
<script src="{% static 'js/recipes/addFormsetField.js' %}"></script>
{% endblock scripts %}
Choices.py
from django.db import models
class WeightUnitChoices(models.TextChoices):
GRAMS = "gram", "gram"
OZ = "ounce", "ounce"
LBS = "pound", "pound"
KG = "kilogram", "kilogram"
T = "tonne", "tonne"
class VolumeUnitChoices(models.TextChoices):
ML = "milliliter", "milliliter"
L = "liter", "liter"
TSP = "teaspoon", "teaspoon"
TBPS = "tablespoon", "tablespoon"
ALL_UNIT_CHOICES = [
(None, "Select Unit"),
("Weight", WeightUnitChoices.choices),
("Volume", VolumeUnitChoices.choices),
]
def get_unit_group(unit):
"""returns the unit group to which unit belongs to"""
match unit:
case unit if unit in dict(WeightUnitChoices.choices):
return WeightUnitChoices.choices
case unit if unit in dict(VolumeUnitChoices.choices):
return VolumeUnitChoices.choices
case _:
# Return all available units
return ALL_UNIT_CHOICES
Screenshot

Displaying get_context_data in template Django

I am trying to display the get_context_data on the template. I have a method on the model class that I need to call from ProfileView which has two different models. For the Profile View I have Profile Model and for the shippingaddress view I have ShippingAddress Model. And these models are from two different app. I tried the function below and it does not show any error, but when I tried to call it in the Template, It does not show the method.
Views.py
class ProfileView(LoginRequiredMixin, DetailView):
model = Profile
template_name = "account/profile.html"
success_url = "/"
def get_context_data(self, *args, **kwargs):
context = super(ProfileView, self).get_context_data(**kwargs)
context['shipping'] = ShippingAddress.objects.filter(user=self.request.user)
return context
Template code
{{object.get_full_address}}
Models.py
class ShippingAddress(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
phone_number = PhoneNumberField(null=True, blank=True)
street_address = models.CharField(max_length=300)
province = models.CharField(max_length=300)
city = models.CharField(max_length=50)
country = models.CharField(max_length=50)
zip_code = models.CharField(max_length=10)
def __str__(self):
return str(self.user)
def get_phone_number(self):
return self.phone_number
#property
def get_full_address(self):
return f"{self.street_address}, {self.province}, {self.city}, {self.country}, {self.zip_code}"
object is the context variable that DetailView will add to the context. For your view this would be an instance of Profile. You pass a queryset into the context with the name shipping so you can loop over that:
{% for shipping_address in shipping %}
{{ shipping_address.get_full_address }}
{% endfor %}
Note: You need to loop because one user has multiple Shipping Addresses according to your models.
Note: Also you didn't need to override get_context_data you could simply have written:
{% for shipping_address in request.user.shippingaddress_set %}
{{ shipping_address.get_full_address }}
{% endfor %}
Where shippingaddress_set is the related model name with _set
appended. You can change that by setting related_name on your
foreign key.

How to get the id using a get_context_data?

I would like to get the total amount of followers attached to the models using in models :
class Project(models.Model):
owner = models.ForeignKey(User, related_name='project_created_by', on_delete=models.CASCADE)
name = models.CharField(max_length=100)
description = models.TextField(max_length=150, blank=True, null=True)
followers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='followers', blank=True)
created = models.DateTimeField(auto_now_add=True)
last_modefied = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
Here is the class
class ProjectListView(ListView):
template_name = 'projectmanagement/project.html'
context_object_name = 'projects'
def get_queryset(self):
queryset = Project.objects.filter(owner=self.request.user).order_by("name")
return queryset
def get_context_data(self, *args, **kwargs):
context = super(ProjectListView, self).get_context_data(*args, **kwargs)
project = Project.objects.get(pk=12) <-- HERE -->
context['followers'] = project.followers.filter(followers=project).count()
return context
You can .annotate(..) [Django-doc] the queryset of your Product with the number of followers:
from django.db.models import Count
class ProjectListView(ListView):
model = Project
template_name = 'projectmanagement/project.html'
context_object_name = 'projects'
def get_queryset(self):
return super().get_queryset().annotate(
nfollowers=Count('followers')
).filter(
owner=self.request.user
).order_by('name')
Now all projects in the context data will have an extra attribute nfollowers with the number of followers.
You can thus render this for example with:
{% for project in projects %}
{{ project.name }}, followers: {{ project.nfollowers }}<br>
{% endfor %}

Using Multiple URL Parameters to get_object in Class-Based-View

Alright, I'm fairly new to this, I've been working on my Project for a couple months now and I'd like to create URLs that accept multiple parameters to call a View. A sample URL would look like this:
http://www.sample.com/builders//m//
I've got this implemented successfully, by overriding get_object in my DetailView, but I'm wondering if there is a better/easier method for accomplishing this or if this is considered a bad practice. Any guidance would be appreciated.
urls.py
urlpatterns = [
# url(r'^$', builder_list, name='list'),
# url(r'^create/$', builder_create, name='create'),
# url(r'^(?P<slug>[\w-]+)/$', builder_detail, name='detail'),
# url(r'^(?P<slug>[\w-]+)/edit/$', builder_update, name='update'),
# url(r'^(?P<slug>[\w-]+)/delete/$', builder_delete, name='delete'),
# url(r'^$', builder_list, name='sub_list'),
# url(r'^m/create/$', sub_create, name='sub_create'),
url(r'^(?P<builder>[\w-]+)/m/(?P<market>[\w-]+)/$', sub_detail, name='sub_detail'),
# url(r'^m/(?P<slug>[\w-]+)/edit/$', sub_update, name='sub_update'),
# url(r'^m/(?P<slug>[\w-]+)/delete/$', sub_delete, name='sub_delete'),
]
views.py
class BuilderSubDetailView(DetailView):
model = BuilderSub
template_name = "builders/sub_detail.html"
def get_context_data(self, **kwargs):
context = super(BuilderSubDetailView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
print(context)
return context
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
builder = self.kwargs['builder']
builder_id = Builder.objects.filter(slug=builder).first().pk
market = self.kwargs['market']
market_id = Market.objects.filter(slug=market).first().pk
if builder is not None and market is not None:
queryset = BuilderSub.objects.filter(parent=builder_id).filter(market=market_id)
# If none of those are defined, it's an error.
if builder is None or market is None:
raise AttributeError("Generic detail view %s must be called with "
"Builder and Market"
% self.__class__.__name__)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404("No %(verbose_name)s found matching the query") % \
{'verbose_name': queryset.model._meta.verbose_name}
return obj
And models.py for reference -- also is there any problem with my get_absolute_url function?
class Builder(models.Model):
added_by = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
company_name = models.CharField(max_length=80, help_text="Full Company Name", unique=True)
short_name = models.CharField(help_text="Short Company Name", max_length=30)
slug = models.SlugField(unique=True)
website = models.CharField(max_length=80, help_text="Format: www.[website].com")
logo = models.ImageField(blank=True, null=True)
timestamp = models.DateTimeField(auto_now_add=True)
info = RedactorField(verbose_name=u'Company Info')
def show_website_url(self):
return format_html("<a href='{url}'>{url}</a>", url=self.website)
def __str__(self):
return self.short_name
class BuilderSub(models.Model):
parent = models.ForeignKey(Builder)
market = models.ForeignKey(Market, null=True, blank=True)
details = RedactorField(verbose_name=u'Details', blank=True, null=True)
main_contact = models.ForeignKey(Person, blank=True, null=True)
def __str__(self):
return "{}: {} - {}".format(self.pk, self.market.name, self.parent.short_name)
def get_absolute_url(self):
return reverse('builders:sub_detail', kwargs={'market': self.market.slug, 'builder': self.parent.slug})
def pre_save_builder_reciever(sender, instance, *args, **kwargs):
instance.slug = slugify(instance.short_name)
pre_save.connect(pre_save_builder_reciever, sender=Builder)
I'm not 100% sure I'm my BuilderSub Model is the appropriate way to handle the Relationship between the overall Builder (company) and the Markets they serve so any guidance there would be appreciated as well.
Yes there is indeed a more ethical way to do this. DetailView is meant to deal with only one object. ListView however gets the job done!
I have replaced builder and market with city and category.
I am also a beginner. Hope I have answered your question :)
views.py
class EntryListView(generic.ListView):
template_name = 'myapp/category.html'
context_object_name = 'entry'
def get_queryset(self):
city_id = self.kwargs['city']
category_id = self.kwargs['category']
entry = Entry.objects.all().filter(city=city_id).filter(category=category_id)
return entry
urls.py
url(r'^(?P<city>[0-9]+)/(?P<category>[0-9]+)/$', views.EntryListView.as_view(), name='entry'),
category.html
{% extends 'myapp/base.html' %}
{% block body %}
<table>
{% for new in entry %}
<tr>
<td>
<img src = "{{new.image_url}}">
<br>
<b>Name :</b> {{new.name}}<br>
{% if new.phone %}
<B>Phone No. :</B> {{new.phone}}<br>
{% endif %}
<b>Address :</b> {{new.address}}<br>
</td>
</tr>
{% endfor %}
{% endblock %}
models.py
class Entry(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=250)
phone = models.IntegerField(null=True)
address = models.CharField(max_length=250)
image_url = models.CharField(max_length=500)
def __str__(self):
return self.name

Cannot resolve keyword u'slug' into field error in Django?

I am getting the following error:
FieldError at /blog/1/first-post/
Cannot resolve keyword u'slug' into field. Choices are: article, date, id, likes
Request Method: GET
Request URL: http://127.0.0.1:8000/blog/1/first-post/
Django Version: 1.6.2
Exception Type: FieldError
Exception Value:
Cannot resolve keyword u'slug' into field. Choices are: article, date, id, likes
My model:
class Article(models.Model):
title = models.CharField(max_length=20)
body = models.TextField()
image = models.ImageField(upload_to="/", blank=True, null=True)
slug = models.SlugField()
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.title)
super(Article, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('article_detail', kwargs={'slug':self.slug, 'id':self.id})
def __unicode__(self):
return self.title
class Detail(models.Model):
article = models.ForeignKey(Article)
date = models.DateField()
likes = models.IntegerField()
def __unicode__(self):
return "%s %s" % (self.article.title, self.likes)
def get_absolute_url(self):
return reverse('detail_article', kwargs={'id':self.id})
View:
class ArticleDetail(DetailView):
model = Detail
template_name = "article_detail.html"
context_object_name = "details"
def get_queryset(self):
print self.kwargs['slug']
a = Article.objects.get(slug=self.kwargs['slug'])
# print Details.object.get()
# print Detail.objects.filter(article__slug=self.kwargs['slug']) fails with same error
return Detail.objects.filter(article=a)
urls.py (this is inside by blog app):
urlpatterns = patterns('',
url(r'all$', ArticleList.as_view(), name='blog_all'),
url(r'^(?P<id>\d+)/(?P<slug>[-\w\d]+)/$', ArticleDetail.as_view(), name='article_detail'),
url(r'^detail/?(P<id?\d+)/$', DetailArticle.as_view(), name='detail_article'),
url(r'^create$', ArticleCreateView.as_view(), name='blog_create'),
)
Basically the detailView of an article instance will display the contents of detail model that has foreignkey relationship to article model. It is not the traditional way where the detail view of article instance displays that instance.
Template here:
{% extends "base.html" %}
{% block content %}
{% for detail in details %}
<p>{{ detail.article.title }}</p>
<p>{{ detail.date }}</p>
<p>{{ detail.likes }}</p>
{% endfor %}
{% endblock %}
overriding the get_object(self, queryset=None) method instead of DetailView get_queryset(self) is an easier solution
class ArticleDetail(DetailView):
model = Detail
template_name = "article_detail.html"
context_object_name = "details"
def get_object(self, queryset=None):
slug = self.kwargs['slug']
a_obj = Article.objects.get(slug=slug)
try:
d_obj = Detail.objects.get(article=a_obj)
except Detail.DoesNotExist:
d_obj = None
except Detail.MultipleObjectsReturned:
#select the apt object
return d_obj
Solution: you need to rename slug parameter in url to other name, or in your view set slug_url_kwarg some other value - not 'slug'
Explanation:
When you add to url, django tries to get object by slug and your model Detail has no slug field.
Link to django code: https://github.com/django/django/blob/master/django/views/generic/detail.py#L33
UPDATE
in SingleObjectMixin:
slug = self.kwargs.get(self.slug_url_kwarg, None)
...
elif slug is not None:
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
so django gets slug from your url, tries to get slug field from Detail model and fails
Your view need to rewrite slug_url_kwarg attribute:
class ArticleDetail(DetailView):
model = Detail
template_name = "article_detail.html"
context_object_name = "details"
slug_url_kwarg = "not_slug" # this attribute
def get_queryset(self):
print self.kwargs['slug']
a = Article.objects.get(slug=self.kwargs['slug'])
# print Details.object.get()
# print Detail.objects.filter(article__slug=self.kwargs['slug']) fails with same error
return Detail.objects.filter(article=a)
but I think better way is to change to attribute in your url:
url(r'^(?P<id>\d+)/(?P<article_slug>[-\w\d]+)/$', ArticleDetail.as_view(), name='article_detail'),
and get article_slug from view kwargs